Skip to content

Commit

Permalink
add TakingFee hook
Browse files Browse the repository at this point in the history
  • Loading branch information
Jun1on committed Jun 7, 2024
1 parent 5082779 commit 7533101
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 0 deletions.
88 changes: 88 additions & 0 deletions contracts/hooks/examples/TakingFee.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// 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 {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {Currency} 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";

contract TakingFee is BaseHook, Owned {
using PoolIdLibrary for PoolKey;
using SafeCast for uint256;

uint128 private constant TOTAL_BIPS = 10000;
uint128 private constant MAX_BIPS = 100;
uint128 public swapFeeBips;
address public treasury = msg.sender;

constructor(
IPoolManager _poolManager,
uint128 _swapFeeBips,
address _treasury
) BaseHook(_poolManager) Owned(msg.sender) {
swapFeeBips = _swapFeeBips;
treasury = _treasury;
}

function getHookPermissions()
public
pure
override
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 override returns (bytes4, int128) {
// fee will be in the unspecified token of the swap
bool specifiedTokenIs0 = (params.amountSpecified < 0 ==
params.zeroForOne);
(Currency feeCurrency, int128 swapAmount) = (specifiedTokenIs0)
? (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;
poolManager.take(feeCurrency, treasury, feeAmount);

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

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

function setTreasury(address _treasury) external onlyOwner {
treasury = _treasury;
}
}
101 changes: 101 additions & 0 deletions test/TakingFee.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {Test} from "forge-std/Test.sol";
import {GetSender} from "./shared/GetSender.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {TakingFee} from "../contracts/hooks/examples/TakingFee.sol";
import {TakingFeeImplementation} from "./shared/implementation/TakingFeeImplementation.sol";
import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.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 {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";

contract TakingFeeTest is Test, Deployers {
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;

uint160 constant SQRT_RATIO_10_1 = 250541448375047931186413801569;

address constant TREASURY = address(0x1234567890123456789012345678901234567890);
uint128 private constant TOTAL_BIPS = 10000;

HookEnabledSwapRouter router;
TestERC20 token0;
TestERC20 token1;
TakingFee takingFee = TakingFee(address(uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)));
PoolId id;

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

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

vm.record();
TakingFeeImplementation impl = new TakingFeeImplementation(manager, 25, TREASURY, takingFee);
(, bytes32[] memory writes) = vm.accesses(address(impl));
vm.etch(address(takingFee), address(impl).code);
// for each storage key that was written during the hook implementation, copy the value over
unchecked {
for (uint256 i = 0; i < writes.length; i++) {
bytes32 slot = writes[i];
vm.store(address(takingFee), slot, vm.load(address(impl), slot));
}
}

// key = PoolKey(currency0, currency1, 3000, 60, takingFee);
(key, id) = initPoolAndAddLiquidity(currency0, currency1, takingFee, 3000, SQRT_PRICE_1_1, ZERO_BYTES);

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

function testSwapHooks() public {
// rounding for tests
uint128 ROUND_FACTOR = 8;

// positions were created in setup()
assertEq(currency0.balanceOf(TREASURY), 0);
assertEq(currency1.balanceOf(TREASURY), 0);

// Perform a test swap //
bool zeroForOne = true;
int256 amountSpecified = -1e12; // negative number indicates exact input swap
BalanceDelta swapDelta = swap(key, zeroForOne, amountSpecified, ZERO_BYTES);
// ------------------- //

uint128 output = uint128(swapDelta.amount1());
assertFalse(output == 0);

uint256 expectedFee = output * TOTAL_BIPS/(TOTAL_BIPS - takingFee.swapFeeBips()) - output;

assertEq(currency0.balanceOf(TREASURY), 0);
assertEq(currency1.balanceOf(TREASURY) / ROUND_FACTOR, expectedFee / ROUND_FACTOR);

// Perform a test swap //
bool zeroForOne2 = true;
int256 amountSpecified2 = 1e12; // positive number indicates exact output swap
BalanceDelta swapDelta2 = swap(key, zeroForOne2, amountSpecified2, ZERO_BYTES);
// ------------------- //

uint128 input = uint128(-swapDelta2.amount0());
assertFalse(input == 0);

uint128 expectedFee2 = (input * takingFee.swapFeeBips()) / (TOTAL_BIPS + takingFee.swapFeeBips());

assertEq(currency0.balanceOf(TREASURY) / ROUND_FACTOR, expectedFee2 / ROUND_FACTOR);
assertEq(currency1.balanceOf(TREASURY) / ROUND_FACTOR, expectedFee / ROUND_FACTOR);
}
}
16 changes: 16 additions & 0 deletions test/shared/implementation/TakingFeeImplementation.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 {TakingFee} from "../../../contracts/hooks/examples/TakingFee.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";

contract TakingFeeImplementation is TakingFee {
constructor(IPoolManager _poolManager, uint128 _swapFeeBips, address _treasury, TakingFee addressToEtch) TakingFee(_poolManager, _swapFeeBips, _treasury) {
Hooks.validateHookPermissions(addressToEtch, getHookPermissions());
}

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

0 comments on commit 7533101

Please sign in to comment.