Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
d2e5ed8
Rehypothecation
luiz-lvj Apr 24, 2025
c90b828
adding beforeSwap
luiz-lvj Apr 24, 2025
bd12f3c
add afterSwap
luiz-lvj Apr 24, 2025
d8297de
get currency deltas
luiz-lvj Apr 24, 2025
299fd31
rehypothecated liquidity
luiz-lvj Apr 25, 2025
7f74e51
Merge branch 'master' of github.com:luiz-lvj/uniswap-hooks into rehyp…
luiz-lvj Jun 25, 2025
951c751
new rehypothecation flow
luiz-lvj Jun 25, 2025
9000ea2
update on add and removal of liquidity
luiz-lvj Jun 25, 2025
231946a
use IERC4626
luiz-lvj Jul 3, 2025
ca53b4e
forge install: openzeppelin-contracts
luiz-lvj Jul 18, 2025
a46c6b3
merge master
luiz-lvj Jul 18, 2025
0ef4408
test
luiz-lvj Aug 26, 2025
904af14
add ERC20
luiz-lvj Aug 26, 2025
d6db0a8
forge fmt
luiz-lvj Aug 26, 2025
00225f6
add tests
luiz-lvj Aug 27, 2025
79721ae
add utility library
luiz-lvj Aug 27, 2025
7a8e573
test swap with interest
luiz-lvj Aug 27, 2025
394977c
add docs
luiz-lvj Aug 27, 2025
84e53ea
removed unused imports
luiz-lvj Aug 27, 2025
4d2ce5c
add docs for library
luiz-lvj Aug 27, 2025
d86a054
add to docs
luiz-lvj Aug 27, 2025
46de8a8
lint
luiz-lvj Aug 27, 2025
15cca18
remove console
luiz-lvj Aug 27, 2025
0199ae6
Merge branch 'master' of github.com:luiz-lvj/uniswap-hooks into rehyp…
luiz-lvj Aug 27, 2025
387738a
update rehypothecation hook to lint
luiz-lvj Aug 27, 2025
d579ea7
remove unecessary import
luiz-lvj Aug 27, 2025
d632f71
up
luiz-lvj Aug 27, 2025
8eb5b6f
up
luiz-lvj Aug 27, 2025
37c4d5f
up
luiz-lvj Aug 27, 2025
8d75cc7
up
luiz-lvj Aug 27, 2025
a4baaf4
up
luiz-lvj Aug 27, 2025
818fc88
forge install: v4-periphery
luiz-lvj Aug 27, 2025
7cfe5c8
install v4 periphery
luiz-lvj Aug 27, 2025
3eaf122
up
luiz-lvj Aug 27, 2025
96f8769
up
luiz-lvj Aug 27, 2025
6cf26f8
up
luiz-lvj Aug 27, 2025
ce11fdb
up
luiz-lvj Aug 27, 2025
6c76454
up
luiz-lvj Aug 27, 2025
27b8e2f
up
luiz-lvj Aug 27, 2025
eda9055
up
luiz-lvj Aug 27, 2025
735aab2
up
luiz-lvj Aug 27, 2025
c32c080
up
luiz-lvj Aug 27, 2025
5d3b2cb
up
luiz-lvj Aug 27, 2025
e8d7868
test
luiz-lvj Aug 27, 2025
32d26d1
up
luiz-lvj Aug 27, 2025
15d6857
siplify mock via construction, rename errors
gonzaotc Sep 5, 2025
48c5bc8
up events params
gonzaotc Sep 5, 2025
b0b5911
up natspec
gonzaotc Sep 5, 2025
01e664b
update rehypothecationhook
gonzaotc Sep 5, 2025
e3fb439
merge w origin
gonzaotc Sep 5, 2025
4a0490a
rename getTickUpper and getTickLower
gonzaotc Sep 5, 2025
83b8e42
up
gonzaotc Sep 5, 2025
5722900
up tests imports
gonzaotc Sep 5, 2025
63c0823
move erc4626 logic to mock
gonzaotc Sep 5, 2025
cc2fa7b
merge master
luiz-lvj Sep 5, 2025
a6de5c1
update remappings
luiz-lvj Sep 5, 2025
ee573bc
up
gonzaotc Sep 5, 2025
7eecfb8
simplify function names
gonzaotc Sep 8, 2025
38fd8a0
refactor liquidity to shares
gonzaotc Sep 8, 2025
08ef691
add rebalancing
gonzaotc Sep 10, 2025
4acd611
add abstract vault
gonzaotc Sep 11, 2025
c54d42f
up tests
gonzaotc Sep 15, 2025
dd37725
up
gonzaotc Sep 15, 2025
1bfb924
up
gonzaotc Sep 15, 2025
bd0fe7b
iterate
gonzaotc Sep 15, 2025
52f2f9d
up
gonzaotc Sep 16, 2025
1937a6e
remove rebalancing and refactor liquidity addition and removal
gonzaotc Sep 16, 2025
5c72328
up
gonzaotc Sep 16, 2025
e84fd32
up codespell
gonzaotc Sep 17, 2025
2d81294
remove unused fn and up docs
gonzaotc Sep 17, 2025
eafb8a6
up docs
gonzaotc Sep 17, 2025
f29e167
up tests
gonzaotc Sep 17, 2025
c676aa5
up tests
gonzaotc Sep 17, 2025
40df160
up docs
gonzaotc Sep 17, 2025
8f464d4
lint
gonzaotc Sep 22, 2025
3f99593
remove base test in favor of erc4626 mocked test
gonzaotc Sep 22, 2025
1af1504
up codespell
gonzaotc Sep 22, 2025
c189bc1
up rehypothecation hook docs
gonzaotc Sep 25, 2025
98eec0c
add ReHypothecationHookNativeMock + refactor the hook to handle nativ…
gonzaotc Sep 25, 2025
2a47c59
lint
gonzaotc Sep 25, 2025
5924dbe
add nonReentrant transient modifier to transferFromSenderToHook
gonzaotc Sep 26, 2025
2ca9d04
add nonReentrant modifier to add and remove rehypothecated liquidity …
gonzaotc Oct 1, 2025
9ab90e0
Merge pull request #7 from gonzaotc/rehypothecation-hook
luiz-lvj Oct 1, 2025
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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
url = https://github.com/Uniswap/v4-core
[submodule "lib/v4-periphery"]
path = lib/v4-periphery
url = https://github.com/Uniswap/v4-periphery
url = https://github.com/uniswap/v4-periphery
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
2 changes: 1 addition & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
forge-std/=lib/forge-std/src/
@uniswap/v4-core/=lib/v4-core/
@uniswap/v4-periphery/=lib/v4-periphery/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
2 changes: 2 additions & 0 deletions src/general/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ Ready-to-use hooks built on top of the base and fee abstract contracts
* {LiquidityPenaltyHook}: Hook resistant to just-in-time liquidity attacks
* {AntiSandwichHook}: Hook resistant to sandwich attacks on swaps
* {LimitOrderHook}: Hook to enable limit order placing on liquidity pools
* {ReHypothecationHook}: Hook that enables rehypothecation of liquidity positions.

== Hooks

{{LiquidityPenaltyHook}}
{{AntiSandwichHook}}
{{LimitOrderHook}}
{{ReHypothecationHook}}
469 changes: 469 additions & 0 deletions src/general/ReHypothecationHook.sol

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions src/mocks/ReHypothecationERC4626Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

// External imports
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";

// Internal imports
import {ReHypothecationHook} from "../general/ReHypothecationHook.sol";

/// @title ERC4626Mock
/// @notice A mock implementation of the ERC-4626 yield source.
contract ERC4626YieldSourceMock is ERC4626 {
constructor(IERC20 token) ERC4626(token) ERC20("ERC4626YieldSourceMock", "E4626YS") {}
}

/// @title ReHypothecationERC4626Mock
/// @notice A mock implementation of the ReHypothecationHook for ERC-4626 yield sources.
contract ReHypothecationERC4626Mock is ReHypothecationHook {
using SafeERC20 for IERC20;

/// @dev Error thrown when attempting to use an unsupported currency.
error UnsupportedCurrency();

address private _yieldSource0;
address private _yieldSource1;

constructor(IPoolManager poolManager_, address yieldSource0_, address yieldSource1_)
ReHypothecationHook(poolManager_)
ERC20("ReHypothecationMock", "RHM")
{
_yieldSource0 = yieldSource0_;
_yieldSource1 = yieldSource1_;
}

/// @dev Override to disable native currency, which is not supported by ERC-4626 yield sources.
function _beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96)
internal
override
returns (bytes4)
{
if (key.currency0.isAddressZero()) revert UnsupportedCurrency();
return super._beforeInitialize(sender, key, sqrtPriceX96);
}

/// @inheritdoc ReHypothecationHook
function getCurrencyYieldSource(Currency currency) public view override returns (address) {
PoolKey memory poolKey = getPoolKey();
if (currency == poolKey.currency0) return _yieldSource0;
if (currency == poolKey.currency1) return _yieldSource1;
revert UnsupportedCurrency();
}

/// @inheritdoc ReHypothecationHook
function _depositToYieldSource(Currency currency, uint256 amount) internal virtual override {
address yieldSource = getCurrencyYieldSource(currency);
if (yieldSource == address(0)) revert UnsupportedCurrency();
IERC20(Currency.unwrap(currency)).approve(address(yieldSource), amount);
IERC4626(yieldSource).deposit(amount, address(this));
}

/// @inheritdoc ReHypothecationHook
function _withdrawFromYieldSource(Currency currency, uint256 amount) internal virtual override {
IERC4626 yieldSource = IERC4626(getCurrencyYieldSource(currency));
if (address(yieldSource) == address(0)) revert UnsupportedCurrency();
yieldSource.withdraw(amount, address(this), address(this));
}

/// @inheritdoc ReHypothecationHook
function _getAmountInYieldSource(Currency currency) internal view virtual override returns (uint256 amount) {
IERC4626 yieldSource = IERC4626(getCurrencyYieldSource(currency));
uint256 yieldSourceShares = yieldSource.balanceOf(address(this));
return yieldSource.convertToAssets(yieldSourceShares);
}

/// @dev Override to disable native currency, which is not supported by ERC-4626 yield sources.
receive() external payable override {
revert UnsupportedCurrency();
}

/// @dev Helpers for testing
function getAmountInYieldSource(Currency currency) public view returns (uint256) {
return _getAmountInYieldSource(currency);
}

// Exclude from coverage report
function test() public {}
}
136 changes: 136 additions & 0 deletions src/mocks/ReHypothecationNativeMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

// External imports
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

// Internal imports
import {ReHypothecationHook} from "../general/ReHypothecationHook.sol";
import {ERC4626YieldSourceMock} from "./ReHypothecationERC4626Mock.sol";

/// @notice A mock implementation of a native yield source.
/// NOTE: This mock implementation of a native yield source is for testing purposes only.
contract NativeYieldSourceMock is ERC20 {
using Math for *;

error InvalidAmount();

constructor() ERC20("NativeYieldSourceMock", "NYSM") {}

function totalAssets() public view virtual returns (uint256) {
return address(this).balance;
}

function convertToAssets(uint256 shares) public view virtual returns (uint256) {
return _convertToAssets(shares);
}

function convertToShares(uint256 assets) public view virtual returns (uint256) {
return _convertToShares(assets);
}

function _convertToShares(uint256 assets) internal view virtual returns (uint256) {
return assets.mulDiv(totalSupply(), totalAssets());
}

function _convertToAssets(uint256 shares) internal view virtual returns (uint256) {
return shares.mulDiv(totalAssets(), totalSupply());
}

function deposit(uint256 amount, address to) public payable {
if (msg.value != amount) revert InvalidAmount();
_mint(to, amount);
}

function withdraw(uint256 assets, address to) public payable {
uint256 shares = _convertToShares(assets);
_burn(msg.sender, shares);
payable(to).transfer(assets);
}
}

/// @title ReHypothecationNativeMock
/// @notice A mock implementation of the ReHypothecationHook for a mixed use case of native ETH and ERC20 tokens.
/// The ERC20 is invested into an ERC-4626 yield source, while the native ETH is invested into a native yield source.
contract ReHypothecationNativeMock is ReHypothecationHook {
using SafeERC20 for IERC20;

address private _yieldSource0;
address private _yieldSource1;

/// @dev Error thrown when attempting to use an unsupported currency.
error UnsupportedCurrency();

constructor(IPoolManager poolManager_, address yieldSource0_, address yieldSource1_)
ReHypothecationHook(poolManager_)
ERC20("ReHypothecationMock", "RHM")
{
_yieldSource0 = yieldSource0_;
_yieldSource1 = yieldSource1_;
}

/// @inheritdoc ReHypothecationHook
function getCurrencyYieldSource(Currency currency) public view override returns (address) {
PoolKey memory poolKey = getPoolKey();
if (currency == poolKey.currency0) return _yieldSource0;
if (currency == poolKey.currency1) return _yieldSource1;
revert UnsupportedCurrency();
}

/// @inheritdoc ReHypothecationHook
function _depositToYieldSource(Currency currency, uint256 amount) internal virtual override {
address yieldSource = getCurrencyYieldSource(currency);
if (yieldSource == address(0)) revert UnsupportedCurrency();
if (currency.isAddressZero()) {
NativeYieldSourceMock(yieldSource).deposit{value: amount}(amount, address(this));
} else {
IERC20(Currency.unwrap(currency)).approve(address(yieldSource), amount);
NativeYieldSourceMock(yieldSource).deposit(amount, address(this));
}
}

/// @inheritdoc ReHypothecationHook
function _withdrawFromYieldSource(Currency currency, uint256 amount) internal virtual override {
address yieldSource = getCurrencyYieldSource(currency);
if (address(yieldSource) == address(0)) revert UnsupportedCurrency();
if (currency.isAddressZero()) {
NativeYieldSourceMock(yieldSource).withdraw(amount, address(this));
} else {
ERC4626YieldSourceMock(yieldSource).withdraw(amount, address(this), address(this));
}
}

/// @inheritdoc ReHypothecationHook
function _getAmountInYieldSource(Currency currency) internal view virtual override returns (uint256 amount) {
address yieldSource = getCurrencyYieldSource(currency);
uint256 yieldSourceShares = IERC20(yieldSource).balanceOf(address(this));
return NativeYieldSourceMock(yieldSource).convertToAssets(yieldSourceShares);
}

/// Override required to handle native ETH
function _transferFromSenderToHook(Currency currency, uint256 amount, address sender) internal virtual override {
if (currency.isAddressZero()) {
if (msg.value < amount) revert InvalidMsgValue();
if (msg.value > amount) {
(bool success,) = msg.sender.call{value: msg.value - amount}("");
if (!success) revert RefundFailed();
}
} else {
super._transferFromSenderToHook(currency, amount, sender);
}
}

/// @dev Helpers for testing
function getAmountInYieldSource(Currency currency) public view returns (uint256) {
return _getAmountInYieldSource(currency);
}

// Exclude from coverage report
function test() public {}
}
2 changes: 2 additions & 0 deletions src/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ NOTE: This document is better viewed on the docs page.
Libraries and general purpose utilities to help develop hooks.

* {CurrencySettler}: Library used to interact with the `PoolManager` to settle any open deltas, with support for ERC-6909 and native currencies.
* {LiquidityMath}: Library with helper functions for liquidity math.

== Libraries

{{CurrencySettler}}
{{LiquidityMath}}
4 changes: 2 additions & 2 deletions test/general/LiquidityPenaltyHook.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ contract LiquidityPenaltyHookTest is HookTest, BalanceDeltaAssertions {
// since the ataccker is the only LP, himself is the recipient of the whole donation in the hooked pool
BalanceDelta hookFeeDeltaAfterRemoval =
calculateFeeDelta(manager, key.toId(), address(modifyLiquidityRouter), -600, 600, bytes32(0));
assertAproxEqAbs(hookFeeDeltaAfterRemoval, feeDelta, 1, "Hooked: Attacker received donation");
assertApproxEqAbs(hookFeeDeltaAfterRemoval, feeDelta, 1, "Hooked: Attacker received donation");

// in the unhooked pool, the attacker should have collected the fees during liquidity removal
BalanceDelta noHookFeeDeltaAfterRemoval =
Expand Down Expand Up @@ -278,7 +278,7 @@ contract LiquidityPenaltyHookTest is HookTest, BalanceDeltaAssertions {
BalanceDelta hookDeltaBobRemoval = modifyPoolLiquidity(key, -600, 600, -1e14, bobSalt);
BalanceDelta noHookDeltaBobRemoval = modifyPoolLiquidity(noHookKey, -600, 600, -1e14, bobSalt);

assertAproxEqAbs(
assertApproxEqAbs(
hookDeltaBobRemoval,
noHookDeltaBobRemoval + feeDelta + feeDelta,
1,
Expand Down
Loading
Loading