Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/DRLVaultV3.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/INonfungiblePositionManager.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.sol";
import "@uniswap/v3-core/contracts/libraries/FixedPoint96.sol";
import "./interfaces/IV3SwapRouter.sol";
import "./interfaces/IUniswapV3Factory.sol";
import "./interfaces/IUniswapV3Pool.sol";
import "./interfaces/IDRLFactory.sol";
import "./libraries/PriceTick.sol";
import "./libraries/LiquidityHelper.sol";
interface IWETH9 {
function deposit() external payable;
function withdraw(uint256 wad) external;
}
contract DRLVaultV3 is ReentrancyGuard {
using SafeERC20 for IERC20;
using Strings for uint256;
// Constants
int24 constant MAX_TICK = 887272;
int24 constant MIN_TICK = -887272;
// Initialization flag
bool private initialized;
// Vault factory
address public vaultFactory;
// Vault owner
address public owner;
// NonFungiblePositionManager address
INonfungiblePositionManager public positionManager;
// QuoterV2 address
IQuoterV2 public quoterV2;
// SwapRouter
IV3SwapRouter public swapRouter;
// Uniswap v3 factory
IUniswapV3Factory public uniswapV3Factory;
// WETH address
address public WETH;
// NFT tokenId
uint256 public lpTokenId;
// Vault fee
uint24 public fee;
uint24 public slippageBps;
struct UserLiquidity {
uint256 tokenId;
uint128 liquidity;
}
mapping(address => UserLiquidity) public userLiquidity;
// token0 (sorted)
address public token0;
// token1 (sorted)
address public token1;
// Events
event TokensDeposited(address indexed user, address token, uint256 amount);
event TokensWithdrawn(address indexed user, address token, uint256 amount);
event UserClaimed(address indexed user, address token, uint256 amount);
event FeesCollected(address indexed user, address token, uint256 amount);
event PositionMinted(address indexed user, uint256 tokenId, int24 tickLower, int24 tickUpper);
event PositionRemoved(address indexed user, uint256 tokenId);
event LiquidityRebalanced(
address indexed user, uint256 oldTokenId, uint256 newTokenId, int24 newTickLower, int24 newTickUpper
);
// Errors
error AddLiquidityFailed();
error PoolDoesNotExist();
error NoLiquidityToRebalance();
// Modifiers
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier onlyOperator() {
address operator = IDRLFactory(vaultFactory).getOperator();
require(msg.sender == operator, "Not operator");
_;
}
/// @notice Initialize the vault (replaces constructor for clone pattern)
/// @dev Can only be called once
function initialize(
address _tokenA,
address _tokenB,
uint24 _fee,
address _weth,
address _operator,
address _positionManager,
address _quoterV2,
address _swapRouter,
address _v3Factory,
address _vaultFactory,
address _owner
) external {
require(!initialized, "Already initialized");
initialized = true;
vaultFactory = _vaultFactory;
owner = _owner;
fee = _fee;
WETH = _weth;
(token0, token1) = sortTokens(_tokenA, _tokenB);
positionManager = INonfungiblePositionManager(_positionManager);
quoterV2 = IQuoterV2(_quoterV2);
swapRouter = IV3SwapRouter(_swapRouter);
uniswapV3Factory = IUniswapV3Factory(_v3Factory);
// Default slippage: 50 = 0.5%
slippageBps = 50;
}
/// @notice Add all available liquidity to a new position
/// @param _fee Pool fee tier
/// @param _priceLower Lower price bound
/// @param _priceUpper Upper price bound
function addLiquidityALL(uint24 _fee, uint256 _priceLower, uint256 _priceUpper) external onlyOperator {
require(userLiquidity[owner].tokenId == 0, "LP already exist!");
// Update fee
fee = _fee;
// Get Pool address and verify it exists
address poolAddress = uniswapV3Factory.getPool(token0, token1, _fee);
if (poolAddress == address(0)) revert PoolDoesNotExist();
IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);
int24 currentTick;
uint160 sqrtPriceX96;
(sqrtPriceX96, currentTick,,,,,) = pool.slot0();
int24 _tickSpacing = pool.tickSpacing();
(int24 tickLower, int24 tickUpper) =
calculatePriceToTicker(uint160(_priceLower), uint160(_priceUpper), _tickSpacing);
// Get sqrt ratios
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
require(sqrtRatioAX96 < sqrtPriceX96 && sqrtPriceX96 < sqrtRatioBX96, "Price not in range");
uint256 _totalUSDC = IERC20(token0).balanceOf(address(this));
require(_totalUSDC > 0, "Not enough USDC");
(uint256 amount0, uint256 amount1) =
LiquidityHelper.getLiquidityAmounts(_totalUSDC, sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96);
uint256 requiredUSDC = 0;
uint256 requiredWETH = 0;
if (currentTick < tickLower) {
// Add USDC side liquidity
requiredUSDC = _totalUSDC;
requiredWETH = 0;
} else if (currentTick > tickUpper) {
// Add WETH side liquidity
requiredUSDC = 0;
requiredWETH = swapToWETH(_totalUSDC);
} else {
// Add range liquidity
requiredUSDC = amount0;
requiredWETH = swapToWETH(amount1);
}
IERC20(token0).approve(address(positionManager), requiredUSDC);
IERC20(token1).approve(address(positionManager), requiredWETH);
// Mint position with slippage protection
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: _fee,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: requiredUSDC,
amount1Desired: requiredWETH,
amount0Min: (requiredUSDC * (10000 - slippageBps)) / 10000,
amount1Min: (requiredWETH * (10000 - slippageBps)) / 10000,
recipient: address(this),
deadline: block.timestamp + 300
});
(uint256 _tokenId, uint128 _liquidity,,) = positionManager.mint(params);
lpTokenId = _tokenId;
userLiquidity[owner].tokenId = _tokenId;
userLiquidity[owner].liquidity = _liquidity;
emit PositionMinted(owner, _tokenId, tickLower, tickUpper);
// Swap remaining WETH to USDC
swapToUsdc();
}
/// @notice Rebalance liquidity to a new price range
/// @param _priceLower New lower price bound
/// @param _priceUpper New upper price bound
function rebase(uint256 _priceLower, uint256 _priceUpper) external onlyOperator nonReentrant {
// 1. Check if liquidity exists
if (userLiquidity[owner].tokenId == 0) revert NoLiquidityToRebalance();
uint256 oldTokenId = userLiquidity[owner].tokenId;
// 2. Remove existing liquidity
_removeLiquidity();
// 3. Get current token balances after removing liquidity
uint256 currentToken0 = IERC20(token0).balanceOf(address(this));
uint256 currentToken1 = IERC20(token1).balanceOf(address(this));
require(currentToken0 > 0 || currentToken1 > 0, "No tokens after removal");
// 4. Get pool info
address poolAddress = uniswapV3Factory.getPool(token0, token1, fee);
if (poolAddress == address(0)) revert PoolDoesNotExist();
IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);
int24 currentTick;
uint160 sqrtPriceX96;
(sqrtPriceX96, currentTick,,,,,) = pool.slot0();
int24 _tickSpacing = pool.tickSpacing();
(int24 tickLower, int24 tickUpper) =
calculatePriceToTicker(uint160(_priceLower), uint160(_priceUpper), _tickSpacing);
// Get sqrt ratios
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
require(sqrtRatioAX96 < sqrtPriceX96 && sqrtPriceX96 < sqrtRatioBX96, "Price not in range");
// 5. Calculate total value in token0 terms
uint256 token1ValueInToken0 = currentToken1 > 0 ? getQuoteForWETH(fee, currentToken1) : 0;
uint256 totalValueInToken0 = currentToken0 + token1ValueInToken0;
// 6. Calculate required token amounts for new range
uint256 requiredToken0;
uint256 requiredToken1;
if (currentTick < tickLower) {
// Below range: need 100% token0
requiredToken0 = totalValueInToken0;
requiredToken1 = 0;
// Swap all token1 to token0 if needed
if (currentToken1 > 0) {
uint256 received = _swapExactInput(token1, token0, currentToken1);
currentToken0 = IERC20(token0).balanceOf(address(this));
}
} else if (currentTick > tickUpper) {
// Above range: need 100% token1
requiredToken0 = 0;
requiredToken1 = currentToken1 + (currentToken0 > 0 ? getQuoteForUSDC(fee, currentToken0) : 0);
// Swap all token0 to token1 if needed
if (currentToken0 > 0) {
uint256 received = _swapExactInput(token0, token1, currentToken0);
currentToken1 = IERC20(token1).balanceOf(address(this));
}
} else {
// In range: calculate optimal ratio
(uint256 amount0Needed, uint256 amount1ToSwap) =
LiquidityHelper.getLiquidityAmounts(totalValueInToken0, sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96);
requiredToken0 = amount0Needed;
// Calculate how much token1 we need
uint256 amount1Needed = getQuoteForUSDC(fee, amount1ToSwap);
requiredToken1 = amount1Needed;
// Adjust balances with single swap
if (currentToken0 > amount0Needed) {
// Have excess token0, need more token1
uint256 excessToken0 = currentToken0 - amount0Needed;
uint256 token1Deficit = amount1Needed > currentToken1 ? amount1Needed - currentToken1 : 0;
if (token1Deficit > 0) {
// Calculate how much token0 to swap
uint256 token0ToSwap = excessToken0 < amount1ToSwap ? excessToken0 : amount1ToSwap;
if (token0ToSwap > 0) {
_swapExactInput(token0, token1, token0ToSwap);
}
}
} else if (currentToken1 > amount1Needed) {
// Have excess token1, need more token0
uint256 excessToken1 = currentToken1 - amount1Needed;
uint256 token0Deficit = amount0Needed > currentToken0 ? amount0Needed - currentToken0 : 0;
if (token0Deficit > 0 && excessToken1 > 0) {
// Swap excess token1 to token0
_swapExactInput(token1, token0, excessToken1);
}
}
}
// 7. Get final balances after swaps
uint256 finalToken0 = IERC20(token0).balanceOf(address(this));
uint256 finalToken1 = IERC20(token1).balanceOf(address(this));
// 8. Add liquidity in new range
IERC20(token0).approve(address(positionManager), finalToken0);
IERC20(token1).approve(address(positionManager), finalToken1);
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: fee,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: finalToken0,
amount1Desired: finalToken1,
amount0Min: (finalToken0 * (10000 - slippageBps)) / 10000,
amount1Min: (finalToken1 * (10000 - slippageBps)) / 10000,
recipient: address(this),
deadline: block.timestamp + 300
});
(uint256 _tokenId, uint128 _liquidity,,) = positionManager.mint(params);
lpTokenId = _tokenId;
userLiquidity[owner].tokenId = _tokenId;
userLiquidity[owner].liquidity = _liquidity;
emit LiquidityRebalanced(owner, oldTokenId, _tokenId, tickLower, tickUpper);
// 9. Optionally swap remaining dust back to token0
uint256 remainingToken1 = IERC20(token1).balanceOf(address(this));
if (remainingToken1 > 0) {
swapToUsdc();
}
}
/// @notice Internal function to perform exact input swap
/// @param tokenIn Input token address
/// @param tokenOut Output token address
/// @param amountIn Amount to swap
/// @return amountOut Amount received
function _swapExactInput(address tokenIn, address tokenOut, uint256 amountIn) internal returns (uint256 amountOut) {
if (amountIn == 0) return 0;
IERC20(tokenIn).approve(address(swapRouter), amountIn);
// Get quote for slippage protection
uint256 expectedOut;
if (tokenIn == WETH) {
expectedOut = getQuoteForWETH(fee, amountIn);
} else {
expectedOut = getQuoteForUSDC(fee, amountIn);
}
uint256 minAmountOut = (expectedOut * (10000 - slippageBps)) / 10000;
IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
recipient: address(this),
amountIn: amountIn,
amountOutMinimum: minAmountOut,
sqrtPriceLimitX96: 0
});
amountOut = swapRouter.exactInputSingle(params);
}
/// @notice Operator add liquidity with specific parameters
function addLiquidity(
address _token0,
address _token1,
uint256 amount0ToMint,
uint256 amount1ToMint,
uint24 _fee,
int24 tickLower,
int24 tickUpper
) external onlyOperator {
require(userLiquidity[owner].tokenId == 0, "Vault liquidity already created!");
if ((_token0 != token0 && _token1 != token1) && (_token0 != token1 && _token1 != token0)) {
revert("Liquidity token not supported");
}
if (_token0 == WETH) {
require(address(this).balance >= amount0ToMint, "Not enough WETH balance");
require(IERC20(_token1).balanceOf(address(this)) >= amount1ToMint, "Not enough token balance");
IWETH9(_token0).deposit{value: amount0ToMint}();
uint256 _wethBalance = IERC20(_token0).balanceOf(address(this));
require(
_wethBalance >= amount0ToMint,
string(abi.encodePacked("Token0 Not enough eth balance: ", _wethBalance.toString()))
);
require(IERC20(_token1).balanceOf(address(this)) >= amount1ToMint, "Not enough USDC token");
} else {
IWETH9(_token1).deposit{value: amount1ToMint}();
require(IERC20(_token1).balanceOf(address(this)) >= amount1ToMint, "Token1 Not enough eth balance");
require(IERC20(_token0).balanceOf(address(this)) >= amount0ToMint, "Not enough USDC token");
}
IERC20(_token0).approve(address(positionManager), amount0ToMint);
IERC20(_token1).approve(address(positionManager), amount1ToMint);
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: _fee,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: amount0ToMint,
amount1Desired: amount1ToMint,
amount0Min: (amount0ToMint * (10000 - slippageBps)) / 10000,
amount1Min: (amount1ToMint * (10000 - slippageBps)) / 10000,
recipient: address(this),
deadline: block.timestamp + 300
});
(uint256 _tokenId, uint128 _liquidity,,) = positionManager.mint(params);
lpTokenId = _tokenId;
userLiquidity[owner].tokenId = _tokenId;
userLiquidity[owner].liquidity = _liquidity;
emit PositionMinted(owner, _tokenId, tickLower, tickUpper);
}
/// @notice Operator remove all liquidity
function removeLiquidity() external onlyOperator {
require(userLiquidity[owner].tokenId != 0, "Vault liquidity does not exist");
_removeLiquidity();
swapToUsdc();
}
/// @notice Internal function to remove liquidity
function _removeLiquidity() internal {
uint128 liquidity = userLiquidity[owner].liquidity;
uint256 tokenId = userLiquidity[owner].tokenId;
INonfungiblePositionManager.DecreaseLiquidityParams memory params =
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId, liquidity: liquidity, amount0Min: 0, amount1Min: 0, deadline: block.timestamp + 300
});
positionManager.decreaseLiquidity(params);
_collect();
positionManager.burn(tokenId);
emit PositionRemoved(owner, tokenId);
// Reset liquidity
delete userLiquidity[owner];
}
/// @notice Get vault balance in token0 (USDC)
/// @return amount Balance of token0
function getVaultBalance() external view returns (uint256 amount) {
return IERC20(token0).balanceOf(address(this));
}
/// @notice Owner withdraw tokens
function withdrawTokens(address token, uint256 amount) external onlyOwner nonReentrant {
require(token == token1 || token == token0, "Invalid token");
require(amount > 0, "Invalid amount");
if (token == WETH) {
uint256 balance = IERC20(WETH).balanceOf(address(this));
IWETH9(WETH).withdraw(balance);
require(address(this).balance >= amount, "Insufficient balance");
(bool sent,) = msg.sender.call{value: amount}("");
require(sent, "ETH transfer failed");
} else {
IERC20(token).transfer(msg.sender, amount);
}
emit TokensWithdrawn(msg.sender, token, amount);
}
receive() external payable {}
fallback() external payable {}
/// @notice Deposit tokens into vault
function deposite(uint256 amount1) external payable onlyOwner nonReentrant {
require(amount1 > 0 || msg.value > 0, "No tokens or ETH sent");
if (msg.value > 0) {
emit TokensDeposited(msg.sender, token0 == WETH ? token0 : token1, msg.value);
}
if (amount1 > 0) {
if (token0 != WETH) {
IERC20(token0).transferFrom(msg.sender, address(this), amount1);
emit TokensDeposited(msg.sender, token0, amount1);
} else {
IERC20(token1).transferFrom(msg.sender, address(this), amount1);
emit TokensDeposited(msg.sender, token1, amount1);
}
}
}
/// @notice Sort tokens to ensure token0 < token1
function sortTokens(address tokenA, address tokenB) internal pure returns (address _token0, address _token1) {
require(tokenA != tokenB, "Identical addresses");
require(tokenA != address(0) && tokenB != address(0), "Zero address");
if (tokenA < tokenB) {
(_token0, _token1) = (tokenA, tokenB);
} else {
(_token0, _token1) = (tokenB, tokenA);
}
}
/// @notice Collect fees from position
/// @return amount0 Amount of token0 collected
/// @return amount1 Amount of token1 collected
function _collect() internal returns (uint256 amount0, uint256 amount1) {
require(IERC721(address(positionManager)).ownerOf(lpTokenId) == address(this), "Not NFT owner");
INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams({
tokenId: lpTokenId, recipient: address(this), amount0Max: type(uint128).max, amount1Max: type(uint128).max
});
(amount0, amount1) = positionManager.collect(params);
return (amount0, amount1);
}
/// @notice Owner collect fees
function collectFees() external onlyOwner {
_collect();
uint256 _feesAmount = swapToUsdc();
emit FeesCollected(msg.sender, token0, _feesAmount);
_sendToOwner();
}
/// @notice Get LP token ID
function getLPTokenId() external view onlyOwner returns (uint256) {
require(userLiquidity[owner].tokenId > 0, "Not owned a tokenId");
return lpTokenId;
}
/// @notice Send all tokens to owner
function _sendToOwner() internal {
uint256 usdcBalance = IERC20(token0).balanceOf(address(this));
uint256 wethBalance = IERC20(token1).balanceOf(address(this));
if (usdcBalance > 0) {
TransferHelper.safeTransfer(token0, owner, usdcBalance);
}
if (wethBalance > 0) {
IWETH9(WETH).withdraw(wethBalance);
(bool sent,) = owner.call{value: wethBalance}("");
require(sent, "ETH transfer failed");
}
}
/// @notice Swap all ETH/WETH to USDC
/// @return _amountOut Amount of USDC received
function swapToUsdc() internal returns (uint256 _amountOut) {
uint256 ethBalance = address(this).balance;
uint256 wethBalance = IERC20(WETH).balanceOf(address(this));
if (ethBalance == 0 && wethBalance == 0) return 0;
if (ethBalance > 0) {
IWETH9(WETH).deposit{value: ethBalance}();
}
wethBalance = IERC20(WETH).balanceOf(address(this));
if (wethBalance == 0) return 0;
uint256 expectedAmountOut = getQuoteForWETH(fee, wethBalance);
// Fixed slippage calculation
uint256 _amountOutMinimum = (expectedAmountOut * (10000 - slippageBps)) / 10000;
IERC20(WETH).approve(address(swapRouter), wethBalance);
IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
tokenIn: WETH,
tokenOut: token0,
fee: fee,
recipient: address(this),
amountIn: wethBalance,
amountOutMinimum: _amountOutMinimum,
sqrtPriceLimitX96: 0
});
_amountOut = swapRouter.exactInputSingle(params);
}
/// @notice Swap USDC to WETH
/// @param _amount Amount of USDC to swap
/// @return _amountOut Amount of WETH received
function swapToWETH(uint256 _amount) public returns (uint256 _amountOut) {
if (_amount == 0) return 0;
uint256 tokenBalance;
address tokenIn;
if (token0 == WETH) {
tokenBalance = IERC20(token1).balanceOf(address(this));
require(tokenBalance >= _amount, "Not enough balance");
tokenIn = token1;
IERC20(token1).approve(address(swapRouter), _amount);
} else {
tokenBalance = IERC20(token0).balanceOf(address(this));
require(tokenBalance >= _amount, "Not enough balance");
IERC20(token0).approve(address(swapRouter), _amount);
tokenIn = token0;
}
uint256 expectedAmountOut = getQuoteForUSDC(fee, _amount);
// Fixed slippage calculation
uint256 _amountOutMinimum = (expectedAmountOut * (10000 - slippageBps)) / 10000;
IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: WETH,
fee: fee,
recipient: address(this),
amountIn: _amount,
amountOutMinimum: _amountOutMinimum,
sqrtPriceLimitX96: 0
});
_amountOut = swapRouter.exactInputSingle(params);
}
/// @notice Swap all USDC to WETH
function swapAllToWETH() external onlyOwner {
uint256 tokenBalance;
address tokenIn;
if (token0 == WETH) {
tokenBalance = IERC20(token1).balanceOf(address(this));
tokenIn = token1;
IERC20(token1).approve(address(swapRouter), tokenBalance);
} else {
tokenBalance = IERC20(token0).balanceOf(address(this));
IERC20(token0).approve(address(swapRouter), tokenBalance);
tokenIn = token0;
}
if (tokenBalance == 0) return;
IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: WETH,
fee: fee,
recipient: address(this),
amountIn: tokenBalance,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
swapRouter.exactInputSingle(params);
uint256 wethBalance = IERC20(WETH).balanceOf(address(this));
IWETH9(WETH).withdraw(wethBalance);
}
/// @notice User claim all vault assets
function claim() external onlyOwner {
// Remove liquidity if exists
if (userLiquidity[owner].tokenId != 0) {
_removeLiquidity();
}
// Swap all to USDC
swapToUsdc();
// Transfer all USDC to owner
if (token0 != WETH) {
uint256 tokenBalance = IERC20(token0).balanceOf(address(this));
TransferHelper.safeTransfer(token0, owner, tokenBalance);
emit UserClaimed(msg.sender, token0, tokenBalance);
} else {
uint256 tokenBalance = IERC20(token1).balanceOf(address(this));
TransferHelper.safeTransfer(token1, owner, tokenBalance);
emit UserClaimed(msg.sender, token1, tokenBalance);
}
}
/// @notice Calculate price to ticker
function calculatePriceToTicker(uint160 _lowerPrice, uint160 _upperPrice, int24 _tickSpacing)
public
pure
returns (int24, int24)
{
require(_lowerPrice < _upperPrice, "Invalid price range");
(int24 tickLower, int24 tickUpper) = PriceTick.getTickRangeV2(_lowerPrice, _upperPrice, _tickSpacing);
return (tickLower, tickUpper);
}
/// @notice Get quote for swapping USDC to WETH
function getQuoteForUSDC(uint24 _fee, uint256 _amountIn) public returns (uint256) {
IQuoterV2.QuoteExactInputSingleParams memory quoteParams = IQuoterV2.QuoteExactInputSingleParams({
tokenIn: token0, tokenOut: WETH, fee: _fee, amountIn: _amountIn, sqrtPriceLimitX96: 0
});
(uint256 amountOut,,,) = quoterV2.quoteExactInputSingle(quoteParams);
return amountOut;
}
/// @notice Get quote for swapping WETH to USDC
function getQuoteForWETH(uint24 _fee, uint256 _amountIn) public returns (uint256) {
IQuoterV2.QuoteExactInputSingleParams memory quoteParams = IQuoterV2.QuoteExactInputSingleParams({
tokenIn: WETH, tokenOut: token0, fee: _fee, amountIn: _amountIn, sqrtPriceLimitX96: 0
});
(uint256 amountOut,,,) = quoterV2.quoteExactInputSingle(quoteParams);
return amountOut;
}
/// @notice Get current price quote
function getQuote(uint24 _fee) public returns (uint256) {
IQuoterV2.QuoteExactInputSingleParams memory quoteParams = IQuoterV2.QuoteExactInputSingleParams({
tokenIn: WETH, tokenOut: token0, fee: _fee, amountIn: 1000000000000000000, sqrtPriceLimitX96: 0
});
(uint256 amountOut,,,) = quoterV2.quoteExactInputSingle(quoteParams);
return amountOut;
}
/// @notice Get LP value in USDC
/// @return _value Value of LP in USDC
function getLPValue() external returns (uint256 _value) {
require(userLiquidity[owner].tokenId > 0, "Not valid tokenId");
(,, address _token0, address _token1, uint24 _fee, int24 tickLower, int24 tickUpper, uint128 liquidity,,,,) =
positionManager.positions(userLiquidity[owner].tokenId);
require(liquidity > 0, "No liquidity in this LP");
address poolAddress = uniswapV3Factory.getPool(token0, token1, fee);
IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);
(uint160 sqrtPriceX96,,,,,,) = pool.slot0();
uint160 sqrtLowerX96 = TickMath.getSqrtRatioAtTick(tickLower);
uint160 sqrtUpperX96 = TickMath.getSqrtRatioAtTick(tickUpper);
(uint256 amount0, uint256 amount1) =
LiquidityAmounts.getAmountsForLiquidity(sqrtPriceX96, sqrtLowerX96, sqrtUpperX96, liquidity);
uint8 decimals0 = 6;
uint8 decimals1 = 18;
uint256 priceX96 = (uint256(sqrtPriceX96) * uint256(sqrtPriceX96)) / (1 << 192);
uint256 token1ValueInUSDC = (amount1 * priceX96 * (10 ** decimals0)) / (10 ** decimals1);
uint256 lpValuedUSDC = amount0 + token1ValueInUSDC;
return lpValuedUSDC;
}
/// @notice Get slippage BPS
/// @return _slippageBps Slippage in basis points
function getSlippageBps() external view returns (uint24 _slippageBps) {
return slippageBps;
}
/// @notice Set slippage BPS
/// @param _slippage Slippage in basis points (50 = 0.5%, 500 = 5%)
function setSlippageBps(uint24 _slippage) external onlyOwner {
require(_slippage <= 1000, "Slippage too high"); // Max 10%
slippageBps = _slippage;
}
/// @notice Get operator address
function getOperator() external view returns (address) {
address operator = IDRLFactory(vaultFactory).getOperator();
return operator;
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {StorageSlot} from "./StorageSlot.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*
* IMPORTANT: Deprecated. This storage-based reentrancy guard will be removed and replaced
* by the {ReentrancyGuardTransient} variant in v6.0.
*
* @custom:stateless
*/
abstract contract ReentrancyGuard {
using StorageSlot for bytes32;
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant REENTRANCY_GUARD_STORAGE =
0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
/**
* @dev A `view` only version of {nonReentrant}. Use to block view functions
* from being called, preventing reading from inconsistent contract state.
*
* CAUTION: This is a "view" modifier and does not change the reentrancy
* status. Use it only on view functions. For payable or non-payable functions,
* use the standard {nonReentrant} modifier instead.
*/
modifier nonReentrantView() {
_nonReentrantBeforeView();
_;
}
function _nonReentrantBeforeView() private view {
if (_reentrancyGuardEntered()) {
revert ReentrancyGuardReentrantCall();
}
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
_nonReentrantBeforeView();
// Any calls to nonReentrant after this point will fail
_reentrancyGuardStorageSlot().getUint256Slot().value = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _reentrancyGuardStorageSlot().getUint256Slot().value == ENTERED;
}
function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
return REENTRANCY_GUARD_STORAGE;
}
}
"
},
"src/interfaces/INonfungiblePositionManager.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import "./IPoolInitializer.sol";
import "./IERC721Permit.sol";
import "./IPeripheryPayments.sol";
import "./IPeripheryImmutableState.sol";
import "../libraries/PoolAddress.sol";
/// @title Non-fungible token for positions
/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred
/// and authorized.
interface INonfungiblePositionManager is
IPoolInitializer,
IPeripheryPayments,
IPeripheryImmutableState,
IERC721Metadata,
IERC721Enumerable,
IERC721Permit
{
/// @notice Emitted when liquidity is increased for a position NFT
/// @dev Also emitted when a token is minted
/// @param tokenId The ID of the token for which liquidity was increased
/// @param liquidity The amount by which liquidity for the NFT position was increased
/// @param amount0 The amount of token0 that was paid for the increase in liquidity
/// @param amount1 The amount of token1 that was paid for the increase in liquidity
event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
/// @notice Emitted when liquidity is decreased for a position NFT
/// @param tokenId The ID of the token for which liquidity was decreased
/// @param liquidity The amount by which liquidity for the NFT position was decreased
/// @param amount0 The amount of token0 that was accounted for the decrease in liquidity
/// @param amount1 The amount of token1 that was accounted for the decrease in liquidity
event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
/// @notice Emitted when tokens are collected for a position NFT
/// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior
/// @param tokenId The ID of the token for which underlying tokens were collected
/// @param recipient The address of the account that received the collected tokens
/// @param amount0 The amount of token0 owed to the position that was collected
/// @param amount1 The amount of token1 owed to the position that was collected
event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1);
/// @notice Returns the position information associated with a given token ID.
/// @dev Throws if the token ID is not valid.
/// @param tokenId The ID of the token that represents the position
/// @return nonce The nonce for permits
/// @return operator The address that is approved for spending
/// @return token0 The address of the token0 for a specific pool
/// @return token1 The address of the token1 for a specific pool
/// @return fee The fee associated with the pool
/// @return tickLower The lower end of the tick range for the position
/// @return tickUpper The higher end of the tick range for the position
/// @return liquidity The liquidity of the position
/// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position
/// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position
/// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation
/// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
/// @notice Creates a new position wrapped in a NFT
/// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized
/// a method does not exist, i.e. the pool is assumed to be initialized.
/// @param params The params necessary to mint a position, encoded as `MintParams` in calldata
/// @return tokenId The ID of the token that represents the minted position
/// @return liquidity The amount of liquidity for this position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function mint(MintParams calldata params)
external
payable
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
struct IncreaseLiquidityParams {
uint256 tokenId;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
/// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`
/// @param params tokenId The ID of the token for which liquidity is being increased,
/// amount0Desired The desired amount of token0 to be spent,
/// amount1Desired The desired amount of token1 to be spent,
/// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,
/// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,
/// deadline The time by which the transaction must be included to effect the change
/// @return liquidity The new liquidity amount as a result of the increase
/// @return amount0 The amount of token0 to acheive resulting liquidity
/// @return amount1 The amount of token1 to acheive resulting liquidity
function increaseLiquidity(IncreaseLiquidityParams calldata params)
external
payable
returns (uint128 liquidity, uint256 amount0, uint256 amount1);
struct DecreaseLiquidityParams {
uint256 tokenId;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
/// @notice Decreases the amount of liquidity in a position and accounts it to the position
/// @param params tokenId The ID of the token for which liquidity is being decreased,
/// amount The amount by which liquidity will be decreased,
/// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,
/// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,
/// deadline The time by which the transaction must be included to effect the change
/// @return amount0 The amount of token0 accounted to the position's tokens owed
/// @return amount1 The amount of token1 accounted to the position's tokens owed
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1);
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
/// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
/// @param params tokenId The ID of the NFT for which tokens are being collected,
/// recipient The account that should receive the tokens,
/// amount0Max The maximum amount of token0 to collect,
/// amount1Max The maximum amount of token1 to collect
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
/// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens
/// must be collected first.
/// @param tokenId The ID of the token that is being burned
function burn(uint256 tokenId) external payable;
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/IERC721.sol)
pragma solidity >=0.6.2;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC-721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
if (!_safeTransfer(token, to, value, true)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
if (!_safeTransferFrom(token, from, to, value, true)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _safeTransfer(token, to, value, false);
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _safeTransferFrom(token, from, to, value, false);
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
if (!_safeApprove(token, spender, value, false)) {
if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));
if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the
* return value is optional (but if data is returned, it must not be false).
*
* @param token The token targeted by the call.
* @param to The recipient of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
*/
function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {
bytes4 selector = IERC20.transfer.selector;
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(0x00, selector)
mstore(0x04, and(to, shr(96, not(0))))
mstore(0x24, value)
success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
// if call success and return is true, all is good.
// otherwise (not success or return is not true), we need to perform further checks
if iszero(and(success, eq(mload(0x00), 1))) {
// if the call was a failure and bubble is enabled, bubble the error
if and(iszero(success), bubble) {
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
// if the return value is not true, then the call is only successful if:
// - the token address has code
// - the returndata is empty
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
}
mstore(0x40, fmp)
}
}
/**
* @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return
* value: the return value is optional (but if data is returned, it must not be false).
*
* @param token The token targeted by the call.
* @param from The sender of the tokens
* @param to The recipient of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
*/
function _safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value,
bool bubble
) private returns (bool success) {
bytes4 selector = IERC20.transferFrom.selector;
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(0x00, selector)
mstore(0x04, and(from, shr(96, not(0))))
mstore(0x24, and(to, shr(96, not(0))))
mstore(0x44, value)
success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)
// if call success and return is true, all is good.
// otherwise (not success or return is not true), we need to perform further checks
if iszero(and(success, eq(mload(0x00), 1))) {
// if the call was a failure and bubble is enabled, bubble the error
if and(iszero(success), bubble) {
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
// if the return value is not true, then the call is only successful if:
// - the token address has code
// - the returndata is empty
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
}
mstore(0x40, fmp)
mstore(0x60, 0)
}
}
/**
* @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
* the return value is optional (but if data is returned, it must not be false).
*
* @param token The token targeted by the call.
* @param spender The spender of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or
Submitted on: 2025-11-05 13:14:17
Comments
Log in to comment.
No comments yet.