BullLeverageRouter
Inherits: LeverageRouterBase
Title: BullLeverageRouter
Leverage router for plDXY-BULL positions via Morpho Blue.
Uses Morpho flash loans + Splitter minting to acquire plDXY-BULL, then deposits as Morpho collateral. Close operation uses a single plDXY-BEAR flash mint for simplicity and gas efficiency. Uses Morpho’s fee-free flash loans for capital efficiency.
STATE MACHINE - OPEN LEVERAGE: ┌─────────────────────────────────────────────────────────────────────────┐ │ openLeverage(principal, leverage) │ │ 1. Pull USDC from user │ │ 2. Flash loan additional USDC from Morpho (fee-free) │ │ └──► onMorphoFlashLoan(OP_OPEN) │ │ └──► _executeOpen() │ │ 1. Mint plDXY-BEAR + plDXY-BULL pairs via Splitter │ │ 2. Sell plDXY-BEAR on Curve → USDC │ │ 3. Stake plDXY-BULL → splDXY-BULL │ │ 4. Deposit splDXY-BULL to Morpho (user’s collateral) │ │ 5. Borrow USDC from Morpho to cover flash repayment │ │ 6. Emit LeverageOpened event │ └─────────────────────────────────────────────────────────────────────────┘
STATE MACHINE - CLOSE LEVERAGE (Single Flash Mint): ┌─────────────────────────────────────────────────────────────────────────┐ │ closeLeverage(debtToRepay, collateralToWithdraw) │ │ 1. Flash mint plDXY-BEAR (collateral + extra for debt repayment) │ │ └──► onFlashLoan(OP_CLOSE) │ │ └──► _executeClose() │ │ 1. Sell extra plDXY-BEAR on Curve → USDC │ │ 2. Repay user’s Morpho debt with USDC from sale │ │ 3. Withdraw user’s splDXY-BULL from Morpho │ │ 4. Unstake splDXY-BULL → plDXY-BULL │ │ 5. Redeem plDXY-BEAR + plDXY-BULL → USDC │ │ 6. Buy plDXY-BEAR on Curve to repay flash mint │ │ 7. Transfer remaining USDC to user │ │ 8. Emit LeverageClosed event │ └─────────────────────────────────────────────────────────────────────────┘
STATE MACHINE - ADD COLLATERAL (Morpho Flash Loan): ┌─────────────────────────────────────────────────────────────────────────┐ │ addCollateral(usdcAmount) │ │ 1. Pull USDC from user │ │ 2. Flash loan USDC from Morpho (F = U × bearPrice / bullPrice) │ │ └──► onMorphoFlashLoan(OP_ADD_COLLATERAL) │ │ └──► _executeAddCollateral() │ │ 1. Mint plDXY-BEAR + plDXY-BULL pairs via Splitter │ │ 2. Sell ALL plDXY-BEAR on Curve → USDC │ │ 3. Stake plDXY-BULL → splDXY-BULL │ │ 4. Deposit splDXY-BULL to Morpho (user’s collateral) │ │ 5. Repay flash loan from BEAR sale proceeds │ │ 6. Emit CollateralAdded event │ └─────────────────────────────────────────────────────────────────────────┘
Note: security-contact: contact@plether.com
Constants
SPLITTER
SyntheticSplitter for minting/burning token pairs.
ISyntheticSplitter public immutable SPLITTER
PLDXY_BULL
plDXY-BULL token (collateral for bull positions).
IERC20 public immutable PLDXY_BULL
STAKED_PLDXY_BULL
StakedToken vault for plDXY-BULL (used as Morpho collateral).
IERC4626 public immutable STAKED_PLDXY_BULL
CAP
Protocol CAP price (8 decimals, oracle format).
uint256 public immutable CAP
ORACLE
Oracle for plDXY basket price (returns BEAR price in 8 decimals).
AggregatorV3Interface public immutable ORACLE
SEQUENCER_UPTIME_FEED
Chainlink L2 sequencer uptime feed (address(0) on L1).
AggregatorV3Interface public immutable SEQUENCER_UPTIME_FEED
ORACLE_TIMEOUT
Maximum age for a valid oracle price.
uint256 public constant ORACLE_TIMEOUT = 24 hours
SEQUENCER_GRACE_PERIOD
Grace period after L2 sequencer restarts before accepting prices.
uint256 public constant SEQUENCER_GRACE_PERIOD = 1 hours
OP_REMOVE_COLLATERAL
Operation type: remove collateral (flash mint).
uint8 internal constant OP_REMOVE_COLLATERAL = 3
OP_ADD_COLLATERAL
Operation type: add collateral (Morpho flash loan).
uint8 internal constant OP_ADD_COLLATERAL = 4
Functions
constructor
Deploys BullLeverageRouter with Morpho market configuration.
constructor(
address _morpho,
address _splitter,
address _curvePool,
address _usdc,
address _plDxyBear,
address _plDxyBull,
address _stakedPlDxyBull,
MarketParams memory _marketParams,
address _sequencerUptimeFeed
) LeverageRouterBase(_morpho, _curvePool, _usdc, _plDxyBear);
Parameters
| Name | Type | Description |
|---|---|---|
_morpho | address | Morpho Blue protocol address. |
_splitter | address | SyntheticSplitter contract address. |
_curvePool | address | Curve USDC/plDXY-BEAR pool address. |
_usdc | address | USDC token address. |
_plDxyBear | address | plDXY-BEAR token address. |
_plDxyBull | address | plDXY-BULL token address. |
_stakedPlDxyBull | address | splDXY-BULL staking vault address. |
_marketParams | MarketParams | Morpho market parameters for splDXY-BULL/USDC. |
_sequencerUptimeFeed | address | Chainlink L2 sequencer feed (address(0) on L1/testnet). |
openLeverage
Open a Leveraged plDXY-BULL Position in one transaction.
Mints pairs via Splitter, sells plDXY-BEAR on Curve, deposits plDXY-BULL to Morpho. Uses fixed debt model (same as BEAR router) - Morpho debt equals principal * (leverage - 1).
function openLeverage(
uint256 principal,
uint256 leverage,
uint256 maxSlippageBps,
uint256 minAmountOut,
uint256 deadline
) external nonReentrant whenNotPaused;
Parameters
| Name | Type | Description |
|---|---|---|
principal | uint256 | Amount of USDC user sends. |
leverage | uint256 | Multiplier (e.g. 3x = 3e18). |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points (e.g., 50 = 0.5%). Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
minAmountOut | uint256 | |
deadline | uint256 | Unix timestamp after which the transaction reverts. |
openLeverageWithPermit
Open a leveraged plDXY-BULL position with a USDC permit signature (gasless approval).
function openLeverageWithPermit(
uint256 principal,
uint256 leverage,
uint256 maxSlippageBps,
uint256 minAmountOut,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external nonReentrant whenNotPaused;
Parameters
| Name | Type | Description |
|---|---|---|
principal | uint256 | Amount of USDC user sends. |
leverage | uint256 | Multiplier (e.g. 3x = 3e18). |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points. |
minAmountOut | uint256 | |
deadline | uint256 | Unix timestamp after which the permit and transaction revert. |
v | uint8 | Signature recovery byte. |
r | bytes32 | Signature r component. |
s | bytes32 | Signature s component. |
_openLeverageCore
function _openLeverageCore(
uint256 principal,
uint256 leverage,
uint256 maxSlippageBps,
uint256 minAmountOut,
uint256 deadline
) internal;
closeLeverage
Close a Leveraged plDXY-BULL Position in one transaction.
Uses a single plDXY-BEAR flash mint to unwind positions efficiently. Queries actual debt from Morpho to ensure full repayment even if interest accrued.
function closeLeverage(
uint256 collateralToWithdraw,
uint256 maxSlippageBps,
uint256 minAmountOut,
uint256 deadline
) external nonReentrant whenNotPaused;
Parameters
| Name | Type | Description |
|---|---|---|
collateralToWithdraw | uint256 | Amount of splDXY-BULL shares to withdraw from Morpho. NOTE: This is staked token shares, not underlying plDXY-BULL amount. Use STAKED_PLDXY_BULL.previewRedeem() to convert shares to underlying. |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points. Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
minAmountOut | uint256 | |
deadline | uint256 | Unix timestamp after which the transaction reverts. |
addCollateral
Add collateral to an existing leveraged position.
Uses Morpho flash loan so user’s USDC input ≈ collateral value added. Flow: Flash loan USDC → Mint pairs → Sell ALL BEAR to repay flash loan → Keep BULL as collateral. Formula: flashLoan = userUSDC × bearPrice / bullPrice
function addCollateral(
uint256 usdcAmount,
uint256 maxSlippageBps,
uint256 minAmountOut,
uint256 deadline
) external nonReentrant whenNotPaused;
Parameters
| Name | Type | Description |
|---|---|---|
usdcAmount | uint256 | Amount of USDC representing desired collateral value. |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points. Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
minAmountOut | uint256 | |
deadline | uint256 | Unix timestamp after which the transaction reverts. |
addCollateralWithPermit
Add collateral with a USDC permit signature (gasless approval).
function addCollateralWithPermit(
uint256 usdcAmount,
uint256 maxSlippageBps,
uint256 minAmountOut,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external nonReentrant whenNotPaused;
Parameters
| Name | Type | Description |
|---|---|---|
usdcAmount | uint256 | Amount of USDC representing desired collateral value. |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points. |
minAmountOut | uint256 | |
deadline | uint256 | Unix timestamp after which the permit and transaction revert. |
v | uint8 | Signature recovery byte. |
r | bytes32 | Signature r component. |
s | bytes32 | Signature s component. |
_addCollateralCore
function _addCollateralCore(
uint256 usdcAmount,
uint256 maxSlippageBps,
uint256 minAmountOut,
uint256 deadline
) internal;
removeCollateral
Remove collateral from an existing leveraged position.
Uses flash mint of BEAR to redeem pairs, then buys back BEAR with USDC. Reverts if the resulting position would be unhealthy.
function removeCollateral(
uint256 collateralToWithdraw,
uint256 maxSlippageBps,
uint256 minAmountOut,
uint256 deadline
) external nonReentrant whenNotPaused;
Parameters
| Name | Type | Description |
|---|---|---|
collateralToWithdraw | uint256 | Amount of splDXY-BULL shares to withdraw. NOTE: This is staked token shares, not underlying plDXY-BULL amount. |
maxSlippageBps | uint256 | Maximum slippage tolerance in basis points. Capped at MAX_SLIPPAGE_BPS (1%) to limit MEV extraction. |
minAmountOut | uint256 | |
deadline | uint256 | Unix timestamp after which the transaction reverts. |
onMorphoFlashLoan
Morpho flash loan callback for USDC flash loans (OP_OPEN, OP_ADD_COLLATERAL).
function onMorphoFlashLoan(
uint256 amount,
bytes calldata data
) external override;
Parameters
| Name | Type | Description |
|---|---|---|
amount | uint256 | Amount of USDC borrowed. |
data | bytes | Encoded operation parameters. |
onFlashLoan
ERC-3156 flash loan callback for plDXY-BEAR flash mints.
function onFlashLoan(
address initiator,
address,
uint256 amount,
uint256 fee,
bytes calldata data
) external override returns (bytes32);
Parameters
| Name | Type | Description |
|---|---|---|
initiator | address | Address that initiated the flash loan (must be this contract). |
<none> | address | |
amount | uint256 | Amount of plDXY-BEAR borrowed. |
fee | uint256 | Flash loan fee (always 0 for SyntheticToken). |
data | bytes | Encoded operation parameters. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | CALLBACK_SUCCESS on successful execution. |
_executeOpen
Executes open leverage operation within Morpho flash loan callback.
function _executeOpen(
uint256 loanAmount,
bytes calldata data
) private;
Parameters
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of USDC borrowed from Morpho. |
data | bytes | Encoded parameters (op, user, deadline, principal, leverage, targetDebt, maxSlippageBps, minSwapOut, minAmountOut). |
_executeAddCollateral
Executes add collateral operation within Morpho flash loan callback.
function _executeAddCollateral(
uint256 flashLoanAmount,
bytes calldata data
) private;
Parameters
| Name | Type | Description |
|---|---|---|
flashLoanAmount | uint256 | Amount of USDC flash loaned. |
data | bytes | Encoded parameters (op, user, deadline, usdcAmount, maxSlippageBps, minSwapOut, minAmountOut). |
_executeClose
Executes close leverage operation within plDXY-BEAR flash mint callback.
function _executeClose(
uint256 flashAmount,
uint256 flashFee,
bytes calldata data
) private;
Parameters
| Name | Type | Description |
|---|---|---|
flashAmount | uint256 | Amount of plDXY-BEAR flash minted. |
flashFee | uint256 | Flash mint fee (always 0). |
data | bytes | Encoded parameters (op, user, deadline, collateralToWithdraw, debtToRepay, borrowShares, extraBearForDebt, maxSlippageBps, minAmountOut). |
_executeRemoveCollateral
Executes remove collateral operation within plDXY-BEAR flash mint callback.
function _executeRemoveCollateral(
uint256 flashAmount,
uint256 flashFee,
bytes calldata data
) private;
Parameters
| Name | Type | Description |
|---|---|---|
flashAmount | uint256 | Amount of plDXY-BEAR flash minted. |
flashFee | uint256 | Flash mint fee (always 0). |
data | bytes | Encoded parameters (op, user, deadline, collateralToWithdraw, maxSlippageBps, minAmountOut). |
previewOpenLeverage
Preview the result of opening a leveraged plDXY-BULL position.
BULL leverage requires a larger flash loan than BEAR because minting happens at CAP price, not market price. The flash loan is repaid by: bearSaleProceeds + morphoBorrow. Morpho debt still follows the fixed model: principal * (leverage - 1).
function previewOpenLeverage(
uint256 principal,
uint256 leverage
) external view returns (uint256 loanAmount, uint256 totalUSDC, uint256 expectedPlDxyBull, uint256 expectedDebt);
Parameters
| Name | Type | Description |
|---|---|---|
principal | uint256 | Amount of USDC user will send. |
leverage | uint256 | Multiplier (e.g. 2x = 2e18). |
Returns
| Name | Type | Description |
|---|---|---|
loanAmount | uint256 | Amount of USDC to flash loan. |
totalUSDC | uint256 | Total USDC for minting pairs (principal + loan). |
expectedPlDxyBull | uint256 | Expected plDXY-BULL tokens received. |
expectedDebt | uint256 | Expected Morpho debt (fixed: principal * (leverage - 1)). |
previewCloseLeverage
Preview the result of closing a leveraged plDXY-BULL position.
function previewCloseLeverage(
uint256 debtToRepay,
uint256 collateralToWithdraw
) external view returns (uint256 expectedUSDC, uint256 usdcForBearBuyback, uint256 expectedReturn);
Parameters
| Name | Type | Description |
|---|---|---|
debtToRepay | uint256 | Amount of USDC debt to repay. |
collateralToWithdraw | uint256 | Amount of plDXY-BULL collateral to withdraw. |
Returns
| Name | Type | Description |
|---|---|---|
expectedUSDC | uint256 | Expected USDC from redeeming pairs. |
usdcForBearBuyback | uint256 | Expected USDC needed to buy back plDXY-BEAR. |
expectedReturn | uint256 | Expected USDC returned to user after all repayments. |
previewAddCollateral
Preview the result of adding collateral.
Uses flash loan so user’s USDC input ≈ collateral value added.
function previewAddCollateral(
uint256 usdcAmount
)
external
view
returns (uint256 flashLoanAmount, uint256 totalUSDC, uint256 expectedPlDxyBull, uint256 expectedStakedShares);
Parameters
| Name | Type | Description |
|---|---|---|
usdcAmount | uint256 | Amount of USDC representing desired collateral value. |
Returns
| Name | Type | Description |
|---|---|---|
flashLoanAmount | uint256 | Amount of USDC to flash loan. |
totalUSDC | uint256 | Total USDC for minting pairs. |
expectedPlDxyBull | uint256 | Expected plDXY-BULL tokens to receive. |
expectedStakedShares | uint256 | Expected splDXY-BULL shares to receive. |
previewRemoveCollateral
Preview the result of removing collateral.
function previewRemoveCollateral(
uint256 collateralToWithdraw
)
external
view
returns (
uint256 expectedPlDxyBull,
uint256 expectedUsdcFromBurn,
uint256 usdcForBearBuyback,
uint256 expectedReturn
);
Parameters
| Name | Type | Description |
|---|---|---|
collateralToWithdraw | uint256 | Amount of splDXY-BULL shares to withdraw. |
Returns
| Name | Type | Description |
|---|---|---|
expectedPlDxyBull | uint256 | Expected plDXY-BULL from unstaking. |
expectedUsdcFromBurn | uint256 | Expected USDC from burning pairs. |
usdcForBearBuyback | uint256 | Expected USDC needed to buy back flash-minted BEAR. |
expectedReturn | uint256 | Expected USDC returned to user. |
_calculateOpenParams
Calculates parameters for opening a leveraged position.
function _calculateOpenParams(
uint256 principal,
uint256 leverage
) private view returns (OpenParams memory params);
_getValidatedOraclePrice
Returns validated oracle prices with staleness, sequencer, and CAP checks.
function _getValidatedOraclePrice() private view returns (uint256 bearPrice, uint256 bullPrice);
_estimateBearForUsdcSale
Estimates BEAR needed to sell for a target USDC amount using binary search on Curve.
function _estimateBearForUsdcSale(
uint256 targetUsdc
) private view returns (uint256);
_estimateUsdcForBearBuyback
Estimates USDC needed to buy BEAR using binary search on Curve.
function _estimateUsdcForBearBuyback(
uint256 bearAmount
) private view returns (uint256);
_binarySearchCurve
Binary search for minimum input to Curve such that get_dy(i, j, input) >= targetOut.
function _binarySearchCurve(
uint256 i,
uint256 j,
uint256 oneUnit,
uint256 targetOut
) private view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
i | uint256 | Curve pool input index. |
j | uint256 | Curve pool output index. |
oneUnit | uint256 | One unit of the input token (1e18 for BEAR, 1e6 for USDC). |
targetOut | uint256 | Desired minimum output from the swap. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | Minimum input amount that produces at least targetOut. |
Events
LeverageOpened
Emitted when a leveraged plDXY-BULL position is opened.
event LeverageOpened(
address indexed user,
uint256 principal,
uint256 leverage,
uint256 loanAmount,
uint256 plDxyBullReceived,
uint256 debtIncurred,
uint256 maxSlippageBps
);
LeverageClosed
Emitted when a leveraged plDXY-BULL position is closed.
event LeverageClosed(
address indexed user,
uint256 debtRepaid,
uint256 collateralWithdrawn,
uint256 usdcReturned,
uint256 maxSlippageBps
);
CollateralAdded
Emitted when collateral is added to a position.
Net USDC cost = usdcAmount - usdcReturned (BEAR sale proceeds returned to user).
event CollateralAdded(
address indexed user, uint256 usdcAmount, uint256 usdcReturned, uint256 collateralAdded, uint256 maxSlippageBps
);
CollateralRemoved
Emitted when collateral is removed from a position.
event CollateralRemoved(
address indexed user, uint256 collateralWithdrawn, uint256 usdcReturned, uint256 maxSlippageBps
);
Structs
OpenParams
struct OpenParams {
uint256 targetDebt;
uint256 loanAmount;
uint256 tokensToMint;
uint256 expectedBearSale;
}