Skip to content

Commit

Permalink
finish frontrun hook
Browse files Browse the repository at this point in the history
  • Loading branch information
Jun1on committed Jul 2, 2024
1 parent 01e29c5 commit 357ef47
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {BaseHook} from "./../../contracts/BaseHook.sol";
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";
Expand All @@ -16,6 +16,10 @@ abstract contract FeeTaker is BaseHook {

constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}

/**
* @notice This hook takes a fee from the unspecified token after a swap.
* @dev This can be overridden if more permissions are needed.
*/
function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
Expand All @@ -24,12 +28,12 @@ abstract contract FeeTaker is BaseHook {
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: true,
beforeSwap: false,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterSwapReturnDelta: true,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
Expand Down Expand Up @@ -58,4 +62,43 @@ abstract contract FeeTaker is BaseHook {
(bytes4 selector, int128 amount) = _afterSwap(sender, key, params, delta, hookData);
return (selector, feeAmount.toInt128() + amount);
}

function withdraw(Currency[] calldata currencies) external {
manager.unlock(abi.encode(currencies));
}

function _unlockCallback(bytes calldata rawData) internal override returns (bytes memory) {
Currency[] memory currencies = abi.decode(rawData, (Currency[]));
uint256 length = currencies.length;
for (uint256 i = 0; i < length;) {
uint256 amount = manager.balanceOf(address(this), CurrencyLibrary.toId(currencies[i]));
manager.burn(address(this), CurrencyLibrary.toId(currencies[i]), amount);
manager.take(currencies[i], _recipient(), amount);
unchecked {
++i;
}
}
return ZERO_BYTES;
}

/**
* @dev This is a virtual function that should be overridden so it returns the fee charged for a given amount.
*/
function _feeAmount(int128 amountUnspecified) internal view virtual returns (uint256);

/**
* @dev This is a virtual function that should be overridden so it returns the address to receive the fee.
*/
function _recipient() internal view virtual returns (address);

/**
* @dev This can be overridden to add logic after a swap.
*/
function _afterSwap(address, PoolKey memory, IPoolManager.SwapParams memory, BalanceDelta, bytes calldata)
internal
virtual
returns (bytes4, int128)
{
return (BaseHook.afterSwap.selector, 0);
}
}
35 changes: 35 additions & 0 deletions contracts/hooks/examples/FeeTaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {FeeTaker} from "./FeeTaker.sol";

contract FeeTaking is FeeTaker, Owned {
using SafeCast for uint256;

uint128 private constant TOTAL_BIPS = 10000;
uint128 public immutable swapFeeBips;
address public treasury;

constructor(IPoolManager _poolManager, uint128 _swapFeeBips, address _owner, address _treasury)
FeeTaker(_poolManager)
Owned(_owner)
{
swapFeeBips = _swapFeeBips;
treasury = _treasury;
}

function setTreasury(address _treasury) external onlyOwner {
treasury = _treasury;
}

function _feeAmount(int128 amountUnspecified) internal view override returns (uint256) {
return uint128(amountUnspecified) * swapFeeBips / TOTAL_BIPS;
}

function _recipient() internal view override returns (address) {
return treasury;
}
}
2 changes: 1 addition & 1 deletion contracts/middleware/MiddlewareProtect.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {BaseHook} from "./../BaseHook.sol";
import {ReentrancyState} from "./../libraries/ReentrancyState.sol";

contract MiddlewareProtect is BaseMiddleware {
uint256 public constant gasLimit = 100000;
uint256 public constant gasLimit = 1000000;

error ActionBetweenHook();

Expand Down
2 changes: 1 addition & 1 deletion contracts/middleware/MiddlewareRemove.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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;
uint256 public constant gasLimit = 10000000;

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

Expand Down
71 changes: 41 additions & 30 deletions test/MiddlewareProtectFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ pragma solidity ^0.8.19;

import {Test} from "forge-std/Test.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {FeeTakingLite} from "./middleware/FeeTakingLite.sol";
import {HooksFrontrun} from "./middleware/HooksFrontrun.sol";
import {HooksFrontrunImplementation} from "./shared/implementation/HooksFrontrunImplementation.sol";
import {MiddlewareProtect} from "../contracts/middleware/MiddlewareProtect.sol";
import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
Expand Down Expand Up @@ -37,6 +38,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers {
PoolId id;

MiddlewareProtectFactory factory;
HooksFrontrun hooksFrontrun;

function setUp() public {
deployFreshManagerAndRouters();
Expand All @@ -46,56 +48,65 @@ contract MiddlewareProtectFactoryTest is Test, Deployers {
token0 = TestERC20(Currency.unwrap(currency0));
token1 = TestERC20(Currency.unwrap(currency1));

hooksFrontrun = HooksFrontrun(address(uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG)));
vm.record();
HooksFrontrunImplementation impl = new HooksFrontrunImplementation(manager, hooksFrontrun);
(, bytes32[] memory writes) = vm.accesses(address(impl));
vm.etch(address(hooksFrontrun), address(impl).code);
unchecked {
for (uint256 i = 0; i < writes.length; i++) {
bytes32 slot = writes[i];
vm.store(address(hooksFrontrun), slot, vm.load(address(impl), slot));
}
}

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

factory = new MiddlewareProtectFactory(manager);
}

function testVariousProtectFactory() public {
FeeTakingLite feeTakingLite = new FeeTakingLite(manager);
uint160 flags =
uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG);
(address hookAddress, bytes32 salt) = HookMiner.find(
address(factory),
flags,
type(MiddlewareProtect).creationCode,
abi.encode(address(manager), address(feeTakingLite))
);
testOn(address(feeTakingLite), salt);
function testFrontrun() public {
(PoolKey memory key,) =
initPoolAndAddLiquidity(currency0, currency1, IHooks(address(0)), 100, SQRT_PRICE_1_1, ZERO_BYTES);
BalanceDelta swapDelta = swap(key, true, 0.001 ether, ZERO_BYTES);

HooksRevert hooksRevert = new HooksRevert(manager);
flags = uint160(Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG);
(hookAddress, salt) = HookMiner.find(
address(factory),
flags,
type(MiddlewareProtect).creationCode,
abi.encode(address(manager), address(hooksRevert))
(key,) = initPoolAndAddLiquidity(
currency0, currency1, IHooks(address(hooksFrontrun)), 100, SQRT_PRICE_1_1, ZERO_BYTES
);
testOn(address(hooksRevert), salt);
BalanceDelta swapDelta2 = swap(key, true, 0.001 ether, ZERO_BYTES);

// while both swaps are in the same pool, the second swap is more expensive
assertEq(swapDelta.amount1(), swapDelta2.amount1());
assertTrue(abs(swapDelta.amount0()) < abs(swapDelta2.amount0()));
assertTrue(manager.balanceOf(address(hooksFrontrun), CurrencyLibrary.toId(key.currency0)) > 0);
}

function testVariousProtectFactory() public {
uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG);

HooksOutOfGas hooksOutOfGas = new HooksOutOfGas(manager);
flags = uint160(Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG);
(hookAddress, salt) = HookMiner.find(
(address hookAddress, bytes32 salt) = HookMiner.find(
address(factory),
flags,
type(MiddlewareProtect).creationCode,
abi.encode(address(manager), address(hooksOutOfGas))
abi.encode(address(manager), address(hooksFrontrun))
);
testOn(address(hooksOutOfGas), salt);
testOn(address(hooksFrontrun), salt);
}

// creates a middleware on an implementation
function testOn(address implementation, bytes32 salt) internal {
address hookAddress = factory.createMiddleware(implementation, salt);
MiddlewareProtect middlewareProtect = MiddlewareProtect(payable(hookAddress));

(key, id) = initPoolAndAddLiquidity(
currency0, currency1, IHooks(address(middlewareProtect)), 3000, SQRT_PRICE_1_1, ZERO_BYTES
(key,) = initPoolAndAddLiquidity(
currency0, currency1, IHooks(address(middlewareProtect)), 100, SQRT_PRICE_1_1, ZERO_BYTES
);
swap(key, true, 0.001 ether, ZERO_BYTES);
//vm.expectRevert();
}

removeLiquidity(currency0, currency1, IHooks(address(middlewareProtect)), 3000, SQRT_PRICE_1_1, ZERO_BYTES);

assertEq(factory.getImplementation(hookAddress), implementation);
function abs(int256 x) internal pure returns (uint256) {
return x >= 0 ? uint256(x) : uint256(-x);
}
}
96 changes: 96 additions & 0 deletions test/middleware/HooksFrontrun.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 "./../../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 {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {console} from "forge-std/console.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";

contract HooksFrontrun is BaseHook {
using SafeCast for uint256;

bytes internal constant ZERO_BYTES = bytes("");
uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1;
uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1;

BalanceDelta swapDelta;
IPoolManager.SwapParams swapParams;

constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}

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

function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata)
external
override
onlyByManager
returns (bytes4, BeforeSwapDelta, uint24)
{
swapParams = params;
console.log(params.zeroForOne);
console.logInt(params.amountSpecified);
swapDelta = manager.swap(key, params, ZERO_BYTES);
console.log("beforeDelta");
console.logInt(swapDelta.amount0());
console.logInt(swapDelta.amount1());
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}

function afterSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external override onlyByManager returns (bytes4, int128) {
BalanceDelta afterDelta = manager.swap(
key,
IPoolManager.SwapParams(
!swapParams.zeroForOne,
-swapParams.amountSpecified,
swapParams.zeroForOne ? MAX_PRICE_LIMIT : MIN_PRICE_LIMIT
),
ZERO_BYTES
);
if (swapParams.zeroForOne) {
int256 profit = afterDelta.amount0() + swapDelta.amount0();
if (profit > 0) {
// else hook reverts
manager.mint(address(this), key.currency0.toId(), uint256(profit));
}
} else {
int256 profit = afterDelta.amount1() + swapDelta.amount1();
if (profit > 0) {
// else hook reverts
manager.mint(address(this), key.currency1.toId(), uint256(profit));
}
}
console.log("afterDelta");
console.logInt(afterDelta.amount0());
console.logInt(afterDelta.amount1());
return (BaseHook.afterSwap.selector, 0);
}
}
16 changes: 16 additions & 0 deletions test/shared/implementation/HooksFrontrunImplementation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {BaseHook} from "../../../contracts/BaseHook.sol";
import {HooksFrontrun} from "../../middleware/HooksFrontrun.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";

contract HooksFrontrunImplementation is HooksFrontrun {
constructor(IPoolManager _poolManager, HooksFrontrun addressToEtch) HooksFrontrun(_poolManager) {
Hooks.validateHookPermissions(addressToEtch, getHookPermissions());
}

// make this a no-op in testing
function validateHookAddress(BaseHook _this) internal pure override {}
}

0 comments on commit 357ef47

Please sign in to comment.