Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"LeverageToken.sol": {
"content": "// SPDX-License-Identifier: MIT
/*
Web: https://perpetual-x.com
X: https://x.com/perpetualxeth
*/
pragma solidity ^0.8.20;
import {Ownable} from "solady/src/auth/Ownable.sol";
import {ERC20} from "solady/src/tokens/ERC20.sol";
import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
struct ReserveData {
uint256 configuration;
uint128 liquidityIndex;
uint128 currentLiquidityRate;
uint128 variableBorrowIndex;
uint128 currentVariableBorrowRate;
uint128 currentStableBorrowRate;
uint40 lastUpdateTimestamp;
uint16 id;
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
address interestRateStrategyAddress;
uint128 accruedToTreasury;
uint128 unbacked;
uint128 isolationModeTotalDebt;
}
interface IPool {
function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external;
function repay(address asset, uint256 amount, uint256 rateMode, address onBehalfOf) external returns (uint256);
function withdraw(address asset, uint256 amount, address to) external returns (uint256);
function getUserAccountData(address user) external view returns (
uint256 totalCollateralBase,
uint256 totalDebtBase,
uint256 availableBorrowsBase,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
function getReserveData(address asset) external view returns (ReserveData memory);
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
}
interface IFlashLoanReceiver {
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool);
}
interface IWETH {
function deposit() external payable;
function withdraw(uint256) external;
function approve(address, uint256) external returns (bool);
function balanceOf(address) external view returns (uint256);
}
interface ISwapRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
}
interface IERC20 {
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function latestRoundData() external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
/// @title TaxToken - Token with Built-in Aave Leverage System
/// @notice All-in-one: Token + Tax Collection + Leveraged ETH Strategy
contract LeverageToken is ERC20, Ownable, ReentrancyGuard, IFlashLoanReceiver {
using PoolIdLibrary for PoolKey;
/* ══════════════════════════════════════════════════════════════════════════════ */
/* CONSTANTS */
/* ══════════════════════════════════════════════════════════════════════════════ */
IPositionManager public immutable positionManager;
IAllowanceTransfer public immutable permit2;
IPoolManager public immutable poolManager;
// Aave v3 Pool (Ethereum Mainnet)
IPool public constant AAVE_POOL = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);
IWETH public constant WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
ISwapRouter public constant SWAP_ROUTER = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
// Chainlink Price Feeds (Mainnet)
AggregatorV3Interface public constant ETH_USD_FEED = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
AggregatorV3Interface public constant USDC_USD_FEED = AggregatorV3Interface(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6);
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
uint256 public constant BASIS_POINTS = 10000;
uint256 public constant MIN_HEALTH_FACTOR = 1.1e18; // 1.1 = 110% (10% buffer above liquidation)
uint256 public constant ETH_STALENESS_THRESHOLD = 3900; // 65 minutes (ETH/USD heartbeat is 1 hour)
uint256 public constant USDC_STALENESS_THRESHOLD = 90000; // 25 hours (USDC/USD heartbeat is 24 hours)
/* ══════════════════════════════════════════════════════════════════════════════ */
/* STATE VARIABLES */
/* ══════════════════════════════════════════════════════════════════════════════ */
address public hookAddress;
bool public midSwap;
// Leverage parameters
uint256 public leverageThreshold = 1 ether;
uint256 public targetLeverage = 3;
uint256 public callerRewardBps = 200; // 2% caller reward
uint256 public maxSlippageBps = 400; // 4% slippage tolerance
uint256 public emergencySlippageBps = 1000; // 10% for emergency closes
uint256 public takeProfitThresholdBps = 2000;
uint256 public compoundCallerRewardBps = 50;
uint256 public leverageAllocationBps = 5000; // 50% to leverage
uint256 public accumulatedLeverageFees;
bool public leverageEnabled = true;
uint256 public maxPriceDeviationBps = 500; // 5% max deviation from oracle
bool private isExecuting; // Reentrancy guard for leverage execution
uint256 public totalDeposited; // Track actual ETH deposited for accurate profit calculation
/* ══════════════════════════════════════════════════════════════════════════════ */
/* EVENTS */
/* ══════════════════════════════════════════════════════════════════════════════ */
event FeesDeposited(uint256 amount, uint256 toLeverage, uint256 toContract);
event LeverageExecuted(address indexed caller, uint256 ethAmount, uint256 callerReward);
event PositionClosed(uint256 ethReturned, uint256 profit);
event PositionCompounded(address indexed caller, uint256 profit, uint256 callerReward);
event ETHWithdrawn(address indexed to, uint256 amount);
event DebugFlashLoan(string step, uint256 value);
event DebugSwap(uint256 amountIn, uint256 amountOut);
/* ══════════════════════════════════════════════════════════════════════════════ */
/* ERRORS */
/* ══════════════════════════════════════════════════════════════════════════════ */
error OnlyHook();
error BelowThreshold();
error NoPosition();
error InvalidParameters();
error StalePrice();
error PriceDeviation();
error UnhealthyPosition();
error AlreadyExecuting();
error InsufficientOutput();
/* ══════════════════════════════════════════════════════════════════════════════ */
/* CONSTRUCTOR */
/* ══════════════════════════════════════════════════════════════════════════════ */
constructor(
address _owner,
address _positionManager,
address _permit2,
address _poolManager
) {
positionManager = IPositionManager(_positionManager);
permit2 = IAllowanceTransfer(_permit2);
poolManager = IPoolManager(_poolManager);
_initializeOwner(_owner);
_mint(address(this), MAX_SUPPLY);
}
function name() public pure override returns (string memory) {
return "PerpetualX";
}
function symbol() public pure override returns (string memory) {
return "PERPX";
}
/* ══════════════════════════════════════════════════════════════════════════════ */
/* UNISWAP V4 FUNCTIONS */
/* ══════════════════════════════════════════════════════════════════════════════ */
function loadLiquidity(address _hook) external payable onlyOwner returns (uint256 tokenId) {
hookAddress = _hook;
address tokenA = address(this);
uint160 sqrtPriceX96 = 3014831488601586337191090825970934;
int24 tickLower = -887200;
int24 tickUpper = 210800;
this.approve(address(permit2), type(uint256).max);
permit2.approve(tokenA, address(positionManager), type(uint160).max, type(uint48).max);
permit2.approve(tokenA, address(poolManager), type(uint160).max, type(uint48).max);
PoolKey memory pool = PoolKey({
currency0: Currency.wrap(address(0)),
currency1: Currency.wrap(address(tokenA)),
fee: 0,
tickSpacing: 200,
hooks: IHooks(hookAddress)
});
poolManager.initialize(pool, sqrtPriceX96);
uint128 liquidity = _calculateLiquidity(
sqrtPriceX96,
tickLower,
tickUpper,
0,
balanceOf(address(this))
);
uint256 nextId = positionManager.nextTokenId();
bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));
bytes memory hookData = new bytes(0);
bytes[] memory params = new bytes[](2);
params[0] = abi.encode(pool, tickLower, tickUpper, liquidity, 0, balanceOf(address(this)), address(this), hookData);
params[1] = abi.encode(pool.currency0, pool.currency1);
positionManager.modifyLiquidities(abi.encode(actions, params), block.timestamp + 120);
return nextId;
}
function setMidSwap(bool value) external {
if (msg.sender != hookAddress) revert OnlyHook();
midSwap = value;
}
function depositTaxes() external payable {
if (msg.sender != hookAddress && msg.sender != owner()) revert OnlyHook();
// If called by hook, it has already split the taxes - add everything to leverage
// If called by owner (for testing), do the split here
if (msg.sender == hookAddress) {
// Hook already split - add all to leverage fund
if (msg.value > 0) {
accumulatedLeverageFees += msg.value;
}
emit FeesDeposited(msg.value, msg.value, 0);
} else {
// Owner calling directly (testing) - do split here
if (!leverageEnabled || leverageAllocationBps == 0) {
emit FeesDeposited(msg.value, 0, msg.value);
return;
}
uint256 toLeverage = (msg.value * leverageAllocationBps) / BASIS_POINTS;
uint256 toContract = msg.value - toLeverage;
if (toLeverage > 0) {
accumulatedLeverageFees += toLeverage;
}
emit FeesDeposited(msg.value, toLeverage, toContract);
}
}
/* ══════════════════════════════════════════════════════════════════════════════ */
/* LEVERAGE EXECUTION (PUBLIC) */
/* ══════════════════════════════════════════════════════════════════════════════ */
function executeLeverage() external {
if (isExecuting) revert AlreadyExecuting();
if (accumulatedLeverageFees < leverageThreshold) revert BelowThreshold();
isExecuting = true;
uint256 ethToLeverage = accumulatedLeverageFees;
accumulatedLeverageFees = 0;
uint256 callerReward = (ethToLeverage * callerRewardBps) / BASIS_POINTS;
uint256 amountToLeverage = ethToLeverage - callerReward;
// Track actual deposited amount for profit calculation
totalDeposited += amountToLeverage;
_executeLeverageInternal(amountToLeverage);
emit LeverageExecuted(msg.sender, amountToLeverage, callerReward);
isExecuting = false;
// CEI pattern: external transfer last
SafeTransferLib.forceSafeTransferETH(msg.sender, callerReward);
}
function compoundPosition() external {
(uint256 totalCollateralBase, uint256 totalDebtBase, , , , ) =
AAVE_POOL.getUserAccountData(address(this));
if (totalDebtBase == 0) revert NoPosition();
// Prevent underflow: ensure collateral > debt
if (totalCollateralBase <= totalDebtBase) revert NoPosition();
uint256 netValue = totalCollateralBase - totalDebtBase;
// Use actual deposited amount (in USD terms via oracle)
if (totalDeposited == 0) revert NoPosition();
// Get ETH price to convert deposited ETH to USD (both in 8 decimals)
(uint256 ethPrice, ) = _getOraclePrices();
// totalDeposited is 18 decimals, ethPrice is 8 decimals
// Result should be 8 decimals to match Aave's totalCollateralBase
uint256 depositedValueUSD = (totalDeposited * ethPrice) / 1e10;
// Prevent underflow: ensure there's actual profit
if (netValue <= depositedValueUSD) revert BelowThreshold();
uint256 profitBps = ((netValue - depositedValueUSD) * BASIS_POINTS) / depositedValueUSD;
if (profitBps < takeProfitThresholdBps) revert BelowThreshold();
// Use normal slippage for auto-compound
uint256 totalProfit = _closePositionInternal(maxSlippageBps);
totalDeposited = 0; // Reset since position is closed
uint256 callerReward = (totalProfit * compoundCallerRewardBps) / BASIS_POINTS;
SafeTransferLib.forceSafeTransferETH(msg.sender, callerReward);
uint256 remainingProfit = totalProfit - callerReward;
if (remainingProfit >= leverageThreshold) {
totalDeposited = remainingProfit; // Track new deposit
_executeLeverageInternal(remainingProfit);
} else {
accumulatedLeverageFees += remainingProfit;
}
emit PositionCompounded(msg.sender, totalProfit, callerReward);
}
function closePosition() external onlyOwner {
uint256 profit = _closePositionInternal(emergencySlippageBps);
totalDeposited = 0; // Reset for next position
SafeTransferLib.forceSafeTransferETH(owner(), profit);
emit PositionClosed(profit, profit);
}
/* ══════════════════════════════════════════════════════════════════════════════ */
/* FLASH LOAN CALLBACK */
/* ══════════════════════════════════════════════════════════════════════════════ */
function executeOperation(
address[] calldata /* assets */,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external override returns (bool) {
require(msg.sender == address(AAVE_POOL), "Not Aave");
require(initiator == address(this), "Not initiated by this");
uint256 flashLoanAmount = amounts[0];
uint256 premium = premiums[0];
// Check if this is a close position operation
(uint256 slippageBpsOrEth, bool isClosing) = abi.decode(params, (uint256, bool));
if (isClosing) {
// Handle close position: assets[0] is USDC
return _handleClosePosition(flashLoanAmount, premium, slippageBpsOrEth);
}
// Handle open leverage position: assets[0] is WETH
uint256 originalEth = slippageBpsOrEth;
uint256 totalWeth = originalEth + flashLoanAmount;
emit DebugFlashLoan("Flash loan received", flashLoanAmount);
WETH.approve(address(AAVE_POOL), totalWeth);
AAVE_POOL.supply(address(WETH), totalWeth, address(this), 0);
emit DebugFlashLoan("Supplied to Aave", totalWeth);
(, , uint256 availableBorrow, , , uint256 healthFactor) = AAVE_POOL.getUserAccountData(address(this));
emit DebugFlashLoan("Available borrow USD", availableBorrow);
emit DebugFlashLoan("Health factor after supply", healthFactor);
// Try to borrow - 65% of available for MAXIMUM leverage (10 loop stacking with 1.1 HF)
// availableBorrow is in 8 decimals (USD), USDC is 6 decimals
// So we need to divide by 100 to convert from 8 decimals to 6 decimals
uint256 usdcToBorrow = (availableBorrow * 6500) / BASIS_POINTS / 100; // 65% of available, converted to 6 decimals
emit DebugFlashLoan("Attempting to borrow USDC", usdcToBorrow);
// Try the borrow
try AAVE_POOL.borrow(USDC, usdcToBorrow, 2, 0, address(this)) {
emit DebugFlashLoan("Borrow successful", usdcToBorrow);
// Approve USDC for swap
IERC20(USDC).approve(address(SWAP_ROUTER), usdcToBorrow);
// Swap USDC to WETH - set minimum to 0 for testing
ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({
tokenIn: USDC,
tokenOut: address(WETH),
fee: 3000,
recipient: address(this),
deadline: block.timestamp,
amountIn: usdcToBorrow,
amountOutMinimum: 0, // No minimum for testing
sqrtPriceLimitX96: 0
});
uint256 wethReceived = SWAP_ROUTER.exactInputSingle(swapParams);
emit DebugSwap(usdcToBorrow, wethReceived);
// Supply swapped WETH back to Aave
WETH.approve(address(AAVE_POOL), wethReceived);
AAVE_POOL.supply(address(WETH), wethReceived, address(this), 0);
emit DebugFlashLoan("Re-deposited WETH", wethReceived);
} catch {
// Borrow failed - continue without it
emit DebugFlashLoan("Borrow failed - continuing without leverage", 0);
}
// Calculate flash loan repayment needed
uint256 totalDebt = flashLoanAmount + premium;
// Withdraw ONLY the flash loan repayment amount
// This leaves the leveraged position (swapped WETH) on Aave
uint256 withdrawn = AAVE_POOL.withdraw(address(WETH), totalDebt, address(this));
emit DebugFlashLoan("Withdrawn from Aave for repayment", withdrawn);
// Approve the flash loan repayment
WETH.approve(address(AAVE_POOL), totalDebt);
emit DebugFlashLoan("Repaying flash loan", totalDebt);
// Validate health factor after all operations
(, , , , , uint256 finalHealthFactor) = AAVE_POOL.getUserAccountData(address(this));
if (finalHealthFactor < MIN_HEALTH_FACTOR) revert UnhealthyPosition();
emit DebugFlashLoan("Final health factor", finalHealthFactor);
return true;
}
/* ══════════════════════════════════════════════════════════════════════════════ */
/* VIEW FUNCTIONS */
/* ══════════════════════════════════════════════════════════════════════════════ */
function canExecute() external view returns (bool) {
return accumulatedLeverageFees >= leverageThreshold && !isExecuting;
}
function canCompound() external view returns (bool, uint256 currentProfitBps) {
(uint256 totalCollateralBase, uint256 totalDebtBase, , , , ) =
AAVE_POOL.getUserAccountData(address(this));
if (totalDebtBase == 0) return (false, 0);
if (totalDeposited == 0) return (false, 0);
uint256 netValue = totalCollateralBase - totalDebtBase;
// Get ETH price to convert deposited ETH to USD (both in 8 decimals)
(uint256 ethPrice, ) = _getOraclePrices();
// totalDeposited is 18 decimals, ethPrice is 8 decimals
// Result should be 8 decimals to match Aave's totalCollateralBase
uint256 depositedValueUSD = (totalDeposited * ethPrice) / 1e10;
if (netValue <= depositedValueUSD) return (false, 0);
currentProfitBps = ((netValue - depositedValueUSD) * BASIS_POINTS) / depositedValueUSD;
return (currentProfitBps >= takeProfitThresholdBps, currentProfitBps);
}
function getPositionData() external view returns (
uint256 totalCollateral,
uint256 totalDebt,
uint256 availableBorrow,
uint256 healthFactor
) {
(totalCollateral, totalDebt, availableBorrow, , , healthFactor) =
AAVE_POOL.getUserAccountData(address(this));
}
function getExpectedReward() external view returns (uint256) {
if (accumulatedLeverageFees < leverageThreshold) return 0;
return (accumulatedLeverageFees * callerRewardBps) / BASIS_POINTS;
}
/* ══════════════════════════════════════════════════════════════════════════════ */
/* ADMIN FUNCTIONS */
/* ══════════════════════════════════════════════════════════════════════════════ */
function setLeverageParameters(
uint256 _threshold,
uint256 _leverage,
uint256 _callerRewardBps,
uint256 _maxSlippageBps,
uint256 _takeProfitThresholdBps,
uint256 _compoundCallerRewardBps
) external onlyOwner {
if (_leverage < 1 || _leverage > 5) revert InvalidParameters();
if (_callerRewardBps > 500) revert InvalidParameters();
if (_maxSlippageBps > 1000) revert InvalidParameters();
if (_compoundCallerRewardBps > 200) revert InvalidParameters();
leverageThreshold = _threshold;
targetLeverage = _leverage;
callerRewardBps = _callerRewardBps;
maxSlippageBps = _maxSlippageBps;
takeProfitThresholdBps = _takeProfitThresholdBps;
compoundCallerRewardBps = _compoundCallerRewardBps;
}
function setLeverageAllocation(uint256 _bps) external onlyOwner {
require(_bps <= BASIS_POINTS, "Invalid BPS");
leverageAllocationBps = _bps;
}
/// @notice Set leverage allocation to 100% after taxes normalize to 10/10
/// @dev Call this function once taxes have settled to 10% buy/sell (after 35 min)
/// @dev Sniper sell taxes still go 100% to owner wallet (handled in TaxHook)
function enableFullLeverageAllocation() external onlyOwner {
leverageAllocationBps = 10000; // 100% to leverage fund
}
function toggleLeverage(bool _enabled) external onlyOwner {
leverageEnabled = _enabled;
}
function emergencyWithdrawETH(address _to, uint256 _amount) external onlyOwner {
SafeTransferLib.forceSafeTransferETH(_to, _amount);
emit ETHWithdrawn(_to, _amount);
}
function emergencyWithdrawTokens(address _token) external onlyOwner {
uint256 balance = ERC20(_token).balanceOf(address(this));
if (balance > 0) {
SafeTransferLib.safeTransfer(_token, owner(), balance);
}
}
/* ══════════════════════════════════════════════════════════════════════════════ */
/* INTERNAL FUNCTIONS */
/* ══════════════════════════════════════════════════════════════════════════════ */
function _closePositionInternal(uint256 slippageBps) internal returns (uint256) {
// Get actual USDC debt amount
ReserveData memory usdcReserve = AAVE_POOL.getReserveData(USDC);
uint256 usdcDebt = IERC20(usdcReserve.variableDebtTokenAddress).balanceOf(address(this));
if (usdcDebt == 0) revert NoPosition();
// Use flash loan to close position safely
// Flash loan USDC to repay debt, then withdraw all WETH and sell to repay flash loan
address[] memory assets = new address[](1);
assets[0] = USDC;
uint256[] memory amounts = new uint256[](1);
amounts[0] = usdcDebt;
uint256[] memory modes = new uint256[](1);
modes[0] = 0; // 0 = no debt, just flash loan
bytes memory params = abi.encode(slippageBps, true); // true = closing position
AAVE_POOL.flashLoan(
address(this),
assets,
amounts,
modes,
address(this),
params,
0
);
return address(this).balance;
}
function _handleClosePosition(uint256 flashLoanAmount, uint256 premium, uint256 slippageBps) internal returns (bool) {
// We received USDC flash loan, use it to repay the debt
IERC20(USDC).approve(address(AAVE_POOL), flashLoanAmount);
AAVE_POOL.repay(USDC, flashLoanAmount, 2, address(this));
// Now withdraw ALL WETH collateral (debt is repaid, so we can withdraw everything)
uint256 wethWithdrawn = AAVE_POOL.withdraw(address(WETH), type(uint256).max, address(this));
// Swap WETH → USDC to repay flash loan + premium
uint256 usdcNeeded = flashLoanAmount + premium;
WETH.approve(address(SWAP_ROUTER), wethWithdrawn);
// Get oracle prices for minimum output calculation
(uint256 ethPrice, uint256 usdcPrice) = _getOraclePrices();
// Calculate minimum USDC we expect
uint256 expectedUsdcOut = (wethWithdrawn * ethPrice * 1e6) / (usdcPrice * 1e18);
uint256 minUsdcOut = (expectedUsdcOut * (BASIS_POINTS - slippageBps)) / BASIS_POINTS;
// Make sure we get at least enough to repay flash loan
if (minUsdcOut < usdcNeeded) {
minUsdcOut = usdcNeeded;
}
ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({
tokenIn: address(WETH),
tokenOut: USDC,
fee: 500, // 0.05% pool
recipient: address(this),
deadline: block.timestamp,
amountIn: wethWithdrawn,
amountOutMinimum: minUsdcOut,
sqrtPriceLimitX96: 0
});
uint256 usdcReceived = SWAP_ROUTER.exactInputSingle(swapParams);
// Approve flash loan repayment
IERC20(USDC).approve(address(AAVE_POOL), usdcNeeded);
// Any remaining USDC is profit, convert to ETH
if (usdcReceived > usdcNeeded) {
uint256 profitUsdc = usdcReceived - usdcNeeded;
// Swap profit USDC → WETH
IERC20(USDC).approve(address(SWAP_ROUTER), profitUsdc);
ISwapRouter.ExactInputSingleParams memory profitSwap = ISwapRouter.ExactInputSingleParams({
tokenIn: USDC,
tokenOut: address(WETH),
fee: 500,
recipient: address(this),
deadline: block.timestamp,
amountIn: profitUsdc,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
uint256 profitWeth = SWAP_ROUTER.exactInputSingle(profitSwap);
// Convert to ETH
WETH.withdraw(profitWeth);
}
return true;
}
function _executeLeverageInternal(uint256 ethAmount) internal {
WETH.deposit{value: ethAmount}();
uint256 flashLoanAmount = ethAmount * (targetLeverage - 1);
address[] memory assets = new address[](1);
assets[0] = address(WETH);
uint256[] memory amounts = new uint256[](1);
amounts[0] = flashLoanAmount;
uint256[] memory modes = new uint256[](1);
modes[0] = 0;
bytes memory params = abi.encode(ethAmount, false); // false = opening position
AAVE_POOL.flashLoan(address(this), assets, amounts, modes, address(this), params, 0);
}
function _calculateLiquidity(
uint160 sqrtPriceX96,
int24 tickLower,
int24 tickUpper,
uint256 amount0Desired,
uint256 amount1Desired
) private pure returns (uint128) {
return LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
amount0Desired,
amount1Desired
);
}
/**
* @dev Get validated prices from Chainlink oracles
* @return ethPrice ETH/USD price (8 decimals)
* @return usdcPrice USDC/USD price (8 decimals)
*/
function _getOraclePrices() private view returns (uint256 ethPrice, uint256 usdcPrice) {
// Get ETH/USD price
(
uint80 ethRoundId,
int256 ethAnswer,
,
uint256 ethUpdatedAt,
uint80 ethAnsweredInRound
) = ETH_USD_FEED.latestRoundData();
// Validate ETH price feed
if (ethAnswer <= 0) revert PriceDeviation();
if (ethUpdatedAt == 0 || block.timestamp - ethUpdatedAt > ETH_STALENESS_THRESHOLD) revert StalePrice();
if (ethAnsweredInRound < ethRoundId) revert StalePrice();
// Get USDC/USD price
(
uint80 usdcRoundId,
int256 usdcAnswer,
,
uint256 usdcUpdatedAt,
uint80 usdcAnsweredInRound
) = USDC_USD_FEED.latestRoundData();
// Validate USDC price feed
if (usdcAnswer <= 0) revert PriceDeviation();
if (usdcUpdatedAt == 0 || block.timestamp - usdcUpdatedAt > USDC_STALENESS_THRESHOLD) revert StalePrice();
if (usdcAnsweredInRound < usdcRoundId) revert StalePrice();
ethPrice = uint256(ethAnswer);
usdcPrice = uint256(usdcAnswer);
// Sanity check: USDC should be close to $1 (between $0.95 and $1.05)
if (usdcPrice < 0.95e8 || usdcPrice > 1.05e8) revert PriceDeviation();
}
receive() external payable {}
}"
},
"@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
using SafeCast for uint256;
/// @notice Computes the amount of liquidity received for a given amount of token0 and price range
/// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount0 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
internal
pure
returns (uint128 liquidity)
{
unchecked {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96);
return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
}
}
/// @notice Computes the amount of liquidity received for a given amount of token1 and price range
/// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount1 The amount1 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
internal
pure
returns (uint128 liquidity)
{
unchecked {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
return FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
}
}
/// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtPriceX96 A sqrt price representing the current pool prices
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount of token0 being sent in
/// @param amount1 The amount of token1 being sent in
/// @return liquidity The maximum amount of liquidity received
function getLiquidityForAmounts(
uint160 sqrtPriceX96,
uint160 sqrtPriceAX96,
uint160 sqrtPriceBX96,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
if (sqrtPriceX96 <= sqrtPriceAX96) {
liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
} else if (sqrtPriceX96 < sqrtPriceBX96) {
uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);
liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
} else {
liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
}
}
}
"
},
"@uniswap/v4-core/src/libraries/TickMath.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {BitMath} from "./BitMath.sol";
import {CustomRevert} from "./CustomRevert.sol";
/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
using CustomRevert for bytes4;
/// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
error InvalidTick(int24 tick);
/// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
error InvalidSqrtPrice(uint160 sqrtPriceX96);
/// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
/// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
/// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
int24 internal constant MAX_TICK = 887272;
/// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
int24 internal constant MIN_TICK_SPACING = 1;
/// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
int24 internal constant MAX_TICK_SPACING = type(int16).max;
/// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_PRICE = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
/// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
1461446703485210103287273052203988822378723970342 - 4295128739 - 1;
/// @notice Given a tickSpacing, compute the maximum usable tick
function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MAX_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Given a tickSpacing, compute the minimum usable tick
function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MIN_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
/// at the given tick
function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
unchecked {
uint256 absTick;
assembly ("memory-safe") {
tick := signextend(2, tick)
// mask = 0 if tick >= 0 else -1 (all 1s)
let mask := sar(255, tick)
// if tick >= 0, |tick| = tick = 0 ^ tick
// if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
// either way, |tick| = mask ^ (tick + mask)
absTick := xor(mask, add(mask, tick))
}
if (absTick > uint256(int256(MAX_TICK))) InvalidTick.selector.revertWith(tick);
// The tick is decomposed into bits, and for each bit with index i that is set, the product of 1/sqrt(1.0001^(2^i))
// is calculated (using Q128.128). The constants used for this calculation are rounded to the nearest integer
// Equivalent to:
// price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
// or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
uint256 price;
assembly ("memory-safe") {
price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
}
if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;
assembly ("memory-safe") {
// if (tick > 0) price = type(uint256).max / price;
if sgt(tick, 0) { price := div(not(0), price) }
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtPrice of the output price is always consistent
// `sub(shl(32, 1), 1)` is `type(uint32).max`
// `price + type(uint32).max` will not overflow because `price` fits in 192 bits
sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
}
}
}
/// @notice Calculates the greatest tick value such that getSqrtPriceAtTick(tick) <= sqrtPriceX96
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getSqrtPriceAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the getSqrtPriceAtTick(tick) is less than or equal to the input sqrtPriceX96
function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
unchecked {
// Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
// second inequality must be >= because the price can never reach the price at the max tick
// if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
// if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
}
uint256 price = uint256(sqrtPriceX96) << 32;
uint256 r = price;
uint256 msb = BitMath.mostSignificantBit(r);
if (msb >= 128) r = price >> (msb - 127);
else r = price << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255738958999603826347141; // Q22.128 number
// Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x)
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
// Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when
// sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE
// is changed, this may need to be changed too
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
}
}
}
"
},
"@uniswap/v4-periphery/src/libraries/Actions.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Library to define different pool actions.
/// @dev These are suggested common commands, however additional commands should be defined as required
/// Some of these actions are not supported in the Router contracts or Position Manager contracts, but are left as they may be helpful commands for other peripheral contracts.
library Actions {
// pool actions
// liquidity actions
uint256 internal constant INCREASE_LIQUIDITY = 0x00;
uint256 internal constant DECREASE_LIQUIDITY = 0x01;
uint256 internal constant MINT_POSITION = 0x02;
uint256 internal constant BURN_POSITION = 0x03;
uint256 internal constant INCREASE_LIQUIDITY_FROM_DELTAS = 0x04;
uint256 internal constant MINT_POSITION_FROM_DELTAS = 0x05;
// swapping
uint256 internal constant SWAP_EXACT_IN_SINGLE = 0x06;
uint256 internal constant SWAP_EXACT_IN = 0x07;
uint256 internal constant SWAP_EXACT_OUT_SINGLE = 0x08;
uint256 internal constant SWAP_EXACT_OUT = 0x09;
// donate
// note this is not supported in the position manager or router
uint256 internal constant DONATE = 0x0a;
// closing deltas on the pool manager
// settling
uint256 internal constant SETTLE = 0x0b;
uint256 internal constant SETTLE_ALL = 0x0c;
uint256 internal constant SETTLE_PAIR = 0x0d;
// taking
uint256 internal constant TAKE = 0x0e;
uint256 internal constant TAKE_ALL = 0x0f;
uint256 internal constant TAKE_PORTION = 0x10;
uint256 internal constant TAKE_PAIR = 0x11;
uint256 internal constant CLOSE_CURRENCY = 0x12;
uint256 internal constant CLEAR_OR_TAKE = 0x13;
uint256 internal constant SWEEP = 0x14;
uint256 internal constant WRAP = 0x15;
uint256 internal constant UNWRAP = 0x16;
// minting/burning 6909s to close deltas
// note this is not supported in the position manager or router
uint256 internal constant MINT_6909 = 0x17;
uint256 internal constant BURN_6909 = 0x18;
}
"
},
"@uniswap/v4-core/src/types/PoolId.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "./PoolKey.sol";
type PoolId is bytes32;
/// @notice Library for computing the ID of a pool
library PoolIdLibrary {
/// @notice Returns value equal to keccak256(abi.encode(poolKey))
function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
assembly ("memory-safe") {
// 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
poolId := keccak256(poolKey, 0xa0)
}
}
}
"
},
"@uniswap/v4-core/src/types/PoolKey.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "./Currency.sol";
import {IHooks} from "../interfaces/IHooks.sol";
import {PoolIdLibrary} from "./PoolId.sol";
using PoolIdLibrary for PoolKey global;
/// @notice Returns the key for identifying a pool
struct PoolKey {
/// @notice The lower currency of the pool, sorted numerically
Currency currency0;
/// @notice The higher currency of the pool, sorted numerically
Currency currency1;
/// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
uint24 fee;
/// @notice Ticks that involve positions must be a multiple of tick spacing
int24 tickSpacing;
/// @notice The hooks of the pool
IHooks hooks;
}
"
},
"@uniswap/v4-core/src/types/Currency.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";
import {CustomRevert} from "../libraries/CustomRevert.sol";
type Currency is address;
using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global;
using CurrencyLibrary for Currency global;
function equals(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(other);
}
function greaterThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) > Currency.unwrap(other);
}
function lessThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) < Currency.unwrap(other);
}
function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) >= Currency.unwrap(other);
}
/// @title CurrencyLibrary
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
library CurrencyLibrary {
/// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
error NativeTransferFailed();
/// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
error ERC20TransferFailed();
/// @notice A constant to represent the native currency
Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));
function transfer(Currency currency, address to, uint256 amount) internal {
// altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
// modified custom error selectors
bool success;
if (currency.isAddressZero()) {
assembly ("memory-safe") {
// Transfer the ETH and revert if it fails.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
// revert with NativeTransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
}
} else {
assembly ("memory-safe") {
// Get a pointer to some free memory.
let fmp := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=
and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), currency, 0, fmp, 68, 0, 32)
)
// Now clean the memory we used
mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
}
// revert with ERC20TransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(
Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
);
}
}
}
function balanceOfSelf(Currency currency) internal view returns (uint256) {
if (currency.isAddressZero()) {
return address(this).balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this));
}
}
function balanceOf(Currency currency, address owner) internal view returns (uint256) {
if (currency.isAddressZero()) {
return owner.balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
}
}
function isAddressZero(Currency currency) internal pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
}
function toId(Currency currency) internal pure returns (uint256) {
return uint160(Currency.unwrap(currency));
}
// If the upper 12 bytes are non-zero, they will be zero-ed out
// Therefore, fromId() and toId() are not inverses of each other
function fromId(uint256 id) internal pure returns (Currency) {
return Currency.wrap(address(uint160(id)));
}
}
"
},
"@uniswap/v4-core/src/interfaces/IHooks.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {IPoolManager} from "./IPoolManager.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";
/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
/// See the Hooks library for the full spec.
/// @dev Should only be callable by the v4 PoolManager.
interface IHooks {
/// @notice The hook called before the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @return bytes4 The function selector for the hook
function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);
/// @notice The hook called after the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @param tick The current tick after the state of a pool is initialized
/// @return bytes4 The function selector for the hook
function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
external
returns (bytes4);
/// @notice The hook called before liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeAddLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterAddLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeRemoveLiquidity(
address sender,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity
Submitted on: 2025-10-14 09:50:49
Comments
Log in to comment.
No comments yet.