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": {
"contracts/lvf/LeverageManager.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IDecentralizedIndex} from "../interfaces/IDecentralizedIndex.sol";
import {IDexAdapter} from "../interfaces/IDexAdapter.sol";
import {IFlashLoanRecipient} from "../interfaces/IFlashLoanRecipient.sol";
import {IFlashLoanSource} from "../interfaces/IFlashLoanSource.sol";
import {IFraxlendPair} from "../interfaces/IFraxlendPair.sol";
import {IIndexUtils} from "../interfaces/IIndexUtils.sol";
import {ILeverageFeeProcessor} from "../interfaces/ILeverageFeeProcessor.sol";
import {ILeverageManager} from "../interfaces/ILeverageManager.sol";
import {ILeveragePositions} from "../interfaces/ILeveragePositions.sol";
import {VaultAccount, VaultAccountingLibrary} from "../libraries/VaultAccount.sol";
import {LeverageManagerAccessControl} from "./LeverageManagerAccessControl.sol";
import {LeveragePositionCustodian} from "./LeveragePositionCustodian.sol";
contract LeverageManager is Initializable, LeverageManagerAccessControl, ILeverageManager, IFlashLoanRecipient {
using SafeERC20 for IERC20;
using VaultAccountingLibrary for VaultAccount;
/// @notice IndexUtils contract for handling LP operations and staking
IIndexUtils public indexUtils;
/// @notice Position NFT contract that manages leverage position ownership
ILeveragePositions public positionNFT;
/// @notice Address that receives protocol fees
address public feeReceiver;
/// @notice Fee percentage for opening positions (PRECISION, e.g., 100 = 1%)
uint16 public openFeePerc;
/// @notice Fee percentage for closing positions (PRECISION, e.g., 100 = 1%)
uint16 public closeFeePerc;
/// @notice Mapping from position ID to position properties
/// @dev positionId => position props
mapping(uint256 => LeveragePositionProps) public positionProps;
/// @notice Private variable to track workflow initialization state
bool private _workflowInitialized;
/// @notice Used in calculations for various fees and percentage calculations
uint16 constant PRECISION = 10000;
/// @notice A smart contract to process fees as needed
address public feeProcessor;
/// @notice Leverage factory address for access control
address public override leverageFactory;
/// @notice Modifier to ensure only the position owner can perform certain actions
/// @param _positionId The ID of the position to check ownership for
modifier onlyPositionOwner(uint256 _positionId) {
require(positionNFT.ownerOf(_positionId) == _msgSender(), "A0");
_;
}
/// @notice Modifier to manage workflow state for add/remove leverage operations
/// @param _starting True when starting a workflow, false when ending
/// @dev Prevents reentrancy and ensures proper workflow state management
modifier workflow(bool _starting) {
if (_starting) {
require(!_workflowInitialized, "W0");
_workflowInitialized = true;
} else {
require(_workflowInitialized, "W1");
_workflowInitialized = false;
}
_;
}
/// @notice Modifier to allow only the owner or leverage factory to perform certain actions
modifier onlyLeverageFactoryOrOwner() override {
if (owner() != _msgSender() && leverageFactory != _msgSender()) {
revert OwnableLeverageFactoryUnauthorizedAccount(_msgSender());
}
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice Initializes the LeverageManager contract with required dependencies
/// @param _positionNFT Address of the position NFT contract
/// @param _idxUtils Address of the IndexUtils contract
/// @param _feeReceiver Address that will receive protocol fees
function initialize(address _positionNFT, address _idxUtils, address _feeReceiver) external initializer {
super.initialize();
positionNFT = ILeveragePositions(_positionNFT);
indexUtils = IIndexUtils(_idxUtils);
feeReceiver = _feeReceiver;
}
/// @notice The ```initializePosition``` function initializes a new position and mints a new position NFT
/// @param _pod The pod to leverage against for the new position
/// @param _recipient User to receive the position NFT
/// @param _hasSelfLendingPairPod bool Advanced implementation parameter that determines whether or not the self lending pod's paired LP asset (fTKN) is podded as well
function initializePosition(address _pod, address _recipient, bool _hasSelfLendingPairPod)
external
override
returns (uint256 _positionId)
{
_positionId = _initializePosition(_pod, _recipient, _hasSelfLendingPairPod);
}
/// @notice The ```addLeverage``` function adds leverage to a position (or creates a new one and adds leverage)
/// @param _positionId The NFT ID of an existing position to add leverage to, or 0 if a new position should be created
/// @param _pod The pod to leverage against for the position
/// @param _pTknAmt Amount of pTKN to use to leverage against
/// @param _pairedLpDesired Total amount of pairedLpTkn for the pod to use to add LP for the new position (including _userProvidedDebtAmt)
/// @param _userProvidedDebtAmt Amt of borrow token a user will provide to reduce flash loan amount and ultimately borrowed position LTV
/// @param _hasSelfLendingPairPod bool Advanced implementation parameter that determines whether or not the self lending pod's paired LP asset (fTKN) is podded as well
/// @param _config Extra config to apply when leveraging a position abi.encode(uint256,uint256,uint256)
/// @dev _config[0] == overrideBorrowAmt Override amount to borrow from the lending pair, only matters if max LTV is >50% on the lending pair
/// @dev _config[1] == slippage for the LP execution with 1000 precision (1000 == 100%)
/// @dev _config[2] == deadline LP deadline for the UniswapV2 implementation
function addLeverage(
uint256 _positionId,
address _pod,
uint256 _pTknAmt,
uint256 _pairedLpDesired,
uint256 _userProvidedDebtAmt,
bool _hasSelfLendingPairPod,
bytes memory _config
) external override workflow(true) {
uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _pTknAmt);
_addLeveragePreCallback(
_msgSender(),
_positionId,
_pod,
IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore,
_pairedLpDesired,
_userProvidedDebtAmt,
_hasSelfLendingPairPod,
_config
);
}
/// @notice The ```addLeverageFromTkn``` function adds leverage to a position (or creates a new one and adds leverage) using underlying pod's TKN
/// @param _positionId The NFT ID of an existing position to add leverage to, or 0 if a new position should be created
/// @param _pod The pod to leverage against for the position
/// @param _tknAmt Amount of underlying pod TKN to use to leverage against
/// @param _amtPtknMintMin Amount of minimum pTKN that should be minted from provided underlying TKN
/// @param _pairedLpDesired Total amount of pairedLpTkn for the pod to use to add LP for the new position (including _userProvidedDebtAmt)
/// @param _userProvidedDebtAmt Amt of borrow token a user will provide to reduce flash loan amount and ultimately borrowed position LTV
/// @param _hasSelfLendingPairPod bool Advanced implementation parameter that determines whether or not the self lending pod's paired LP asset (fTKN) is podded as well
/// @param _config Extra config to apply when leveraging a position abi.encode(uint256,uint256,uint256)
/// @dev _config[0] == overrideBorrowAmt Override amount to borrow from the lending pair, only matters if max LTV is >50% on the lending pair
/// @dev _config[1] == slippage for the LP execution with 1000 precision (1000 == 100%)
/// @dev _config[2] == deadline LP deadline for the UniswapV2 implementation
function addLeverageFromTkn(
uint256 _positionId,
address _pod,
uint256 _tknAmt,
uint256 _amtPtknMintMin,
uint256 _pairedLpDesired,
uint256 _userProvidedDebtAmt,
bool _hasSelfLendingPairPod,
bytes memory _config
) external override workflow(true) {
uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
_bondToPod(_msgSender(), _pod, _tknAmt, _amtPtknMintMin);
_addLeveragePreCallback(
_msgSender(),
_positionId,
_pod,
IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore,
_pairedLpDesired,
_userProvidedDebtAmt,
_hasSelfLendingPairPod,
_config
);
}
/// @notice The ```removeLeverage``` function removes leverage from a position
/// @param _positionId The NFT ID for the position
/// @param _borrowSharesAmt Amount of borrow shares to remove from the position by paying back
/// @param _remLevConfig Extra config required for removing leverage
/// @dev _config[0] == _collateralAssetRemoveAmt Amount of collateral asset to remove from the position
/// @dev _config[1] == _podAmtMin Minimum Amount of pTKN to receive on remove LP transaction (slippage)
/// @dev _config[2] == _pairedAssetAmtMin Minimum amount of pairedLpTkn to receive on remove LP transaction (slippage)
/// @dev _config[3] == _podPairedLiquidityPrice18 If we need to swap pTKN for pairedLpTkn to pay back flash loan, this is a 10**18*token1/token0 (decimals NOT removed) price of pod LP
/// @dev _config[4] == _userProvidedDebtAmt Amount of borrow token a user will use to transfer from their wallet to pay back flash loan
function removeLeverage(uint256 _positionId, uint256 _borrowSharesAmt, bytes memory _remLevConfig)
external
override
workflow(true)
{
address _sender = _msgSender();
address _owner = positionNFT.ownerOf(_positionId);
require(
_owner == _sender || positionNFT.getApproved(_positionId) == _sender
|| positionNFT.isApprovedForAll(_owner, _sender),
"A1"
);
address _lendingPair = positionProps[_positionId].lendingPair;
IFraxlendPair(_lendingPair).addInterest(false);
uint256 _borrowAmt = IFraxlendPair(_lendingPair).totalBorrow().toAmount(_borrowSharesAmt, true);
(uint256 _userProvidedDebtAmt, bytes memory _finalRemLevConfig) =
_checkAndResetRemoveLeverageConfigFromBorrowAmt(_borrowAmt, _remLevConfig);
// if additional fees required for flash source, handle that here
_processExtraFlashLoanPayment(_positionId, _sender);
address _borrowTkn = _getBorrowTknForPosition(_positionId);
// needed to repay lending pair asset before removing collateral and unwinding
IERC20(_borrowTkn).safeIncreaseAllowance(_lendingPair, _borrowAmt);
LeverageFlashProps memory _props;
_props.method = FlashCallbackMethod.REMOVE;
_props.positionId = _positionId;
_props.owner = _owner;
_props.sender = _sender;
bytes memory _additionalInfo = abi.encode(_borrowSharesAmt, _finalRemLevConfig);
if (_borrowAmt > _userProvidedDebtAmt) {
IFlashLoanSource(_getFlashSource(_positionId)).flash(
_borrowTkn, _borrowAmt - _userProvidedDebtAmt, address(this), abi.encode(_props, _additionalInfo)
);
} else {
_callback(
abi.encode(
IFlashLoanSource.FlashData(address(this), _borrowTkn, 0, abi.encode(_props, _additionalInfo), 0)
)
);
}
}
/// @notice The ```borrowAssets``` function allows a position owner to borrow more for a position in the position custodian
/// @param _positionId The NFT ID for the position
/// @param _borrowAmt The amount of borrow token to borrow
/// @param _collateralAmt A collateral amount to deposit
/// @param _recipient Where the received assets should go
function borrowAssets(uint256 _positionId, uint256 _borrowAmt, uint256 _collateralAmt, address _recipient)
external
onlyPositionOwner(_positionId)
{
if (_collateralAmt > 0) {
IERC20(_getAspTkn(_positionId)).safeTransferFrom(
_msgSender(), positionProps[_positionId].custodian, _collateralAmt
);
}
LeveragePositionCustodian(positionProps[_positionId].custodian).borrowAsset(
positionProps[_positionId].lendingPair,
_borrowAmt,
_collateralAmt,
openFeePerc > 0 ? address(this) : _recipient
);
if (openFeePerc > 0) {
address _borrowTkn = IFraxlendPair(positionProps[_positionId].lendingPair).asset();
uint256 _openFeeAmt = (_borrowAmt * openFeePerc) / PRECISION;
IERC20(_borrowTkn).safeTransfer(feeReceiver, _openFeeAmt);
IERC20(_borrowTkn).safeTransfer(_recipient, _borrowAmt - _openFeeAmt);
}
}
/// @notice The ```withdrawAssets``` function allows a position owner to withdraw any assets in the position custodian
/// @param _positionId The NFT ID for the position
/// @param _token The token to withdraw assets from
/// @param _recipient Where the received assets should go
/// @param _amount How much to withdraw
function withdrawAssets(uint256 _positionId, address _token, address _recipient, uint256 _amount)
external
onlyPositionOwner(_positionId)
{
LeveragePositionCustodian(positionProps[_positionId].custodian).withdraw(_token, _recipient, _amount);
}
/// @notice The ```callback``` function can only be called within the addLeverage or removeLeverage workflow,
/// @notice and is called by the flash source implementation used to borrow assets to initiate adding or removing lev
/// @param _userData Config/info to unpack and extract individual pieces when adding/removing leverage, see addLeverage and removeLeverage
function callback(bytes memory _userData) external override {
IFlashLoanSource.FlashData memory _d = abi.decode(_userData, (IFlashLoanSource.FlashData));
(LeverageFlashProps memory _posProps,) = abi.decode(_d.data, (LeverageFlashProps, bytes));
require(_getFlashSource(_posProps.positionId) == _msgSender(), "A2");
_callback(_userData);
}
/// @notice Internal callback function that handles flash loan callbacks for add/remove leverage operations
/// @param _userData Encoded flash loan data containing position properties and additional information
/// @dev This function is called after flash loan execution to complete leverage operations
function _callback(bytes memory _userData) internal workflow(false) {
IFlashLoanSource.FlashData memory _d = abi.decode(_userData, (IFlashLoanSource.FlashData));
(LeverageFlashProps memory _posProps,) = abi.decode(_d.data, (LeverageFlashProps, bytes));
address _pod = positionProps[_posProps.positionId].pod;
if (_posProps.method == FlashCallbackMethod.ADD) {
uint256 _ptknRefundAmt = _addLeveragePostCallback(_userData);
if (_ptknRefundAmt > 0) {
IERC20(_pod).safeTransfer(_posProps.owner, _ptknRefundAmt);
}
} else if (_posProps.method == FlashCallbackMethod.REMOVE) {
(uint256 _ptknToUserAmt, uint256 _borrowTknToUser) = _removeLeveragePostCallback(_userData);
if (_ptknToUserAmt > 0) {
if (closeFeePerc > 0) {
uint256 _closePtknTotalFees = (_ptknToUserAmt * closeFeePerc) / PRECISION;
_closePtknTotalFees = _processFees(_pod, _pod, _closePtknTotalFees, false);
_ptknToUserAmt -= _closePtknTotalFees;
}
IERC20(_pod).safeTransfer(_posProps.owner, _ptknToUserAmt);
}
if (_borrowTknToUser > 0) {
address _borrowTkn = _getBorrowTknForPosition(_posProps.positionId);
if (closeFeePerc > 0) {
uint256 _closeBorrowTotalFees = (_borrowTknToUser * closeFeePerc) / PRECISION;
_closeBorrowTotalFees = _processFees(_pod, _borrowTkn, _closeBorrowTotalFees, false);
_borrowTknToUser -= _closeBorrowTotalFees;
}
IERC20(_borrowTkn).safeTransfer(_posProps.owner, _borrowTknToUser);
}
} else {
require(false, "NI");
}
}
/// @notice Internal function to initialize a new leverage position
/// @param _pod The pod address to create the position for
/// @param _recipient The address that will receive the position NFT
/// @param _hasSelfLendingPairPod Whether the self lending pod's paired LP asset is podded
/// @return _positionId The ID of the newly created position
function _initializePosition(address _pod, address _recipient, bool _hasSelfLendingPairPod)
internal
returns (uint256 _positionId)
{
_positionId = positionNFT.mint(_recipient);
LeveragePositionCustodian _custodian = new LeveragePositionCustodian();
positionProps[_positionId] = LeveragePositionProps({
pod: _pod,
lendingPair: lendingPairs[_pod],
custodian: address(_custodian),
isSelfLending: IDecentralizedIndex(_pod).PAIRED_LP_TOKEN() == lendingPairs[_pod],
hasSelfLendingPairPod: _hasSelfLendingPairPod
});
}
/// @notice Internal function to handle extra flash loan payment requirements
/// @param _positionId The position ID to get flash source for
/// @param _user The user address to transfer payment from
/// @dev Some flash loan sources require additional payment tokens beyond the borrowed amount
function _processExtraFlashLoanPayment(uint256 _positionId, address _user) internal {
address _posFlashSrc = _getFlashSource(_positionId);
IFlashLoanSource _flashLoanSource = IFlashLoanSource(_posFlashSrc);
uint256 _flashPaymentAmount = _flashLoanSource.paymentAmount();
if (_flashPaymentAmount > 0) {
address _paymentAsset = _flashLoanSource.paymentToken();
IERC20(_paymentAsset).safeTransferFrom(_user, address(this), _flashPaymentAmount);
IERC20(_paymentAsset).safeIncreaseAllowance(_posFlashSrc, _flashPaymentAmount);
}
}
/// @notice Internal function to handle pre-callback logic for adding leverage
/// @param _sender The address initiating the leverage addition
/// @param _positionId The position ID (0 for new position)
/// @param _pod The pod address for the position
/// @param _pTknAmt Amount of pod tokens to use
/// @param _pairedLpDesired Total amount of paired LP tokens desired
/// @param _userProvidedDebtAmt Amount of debt token provided by user
/// @param _hasSelfLendingPairPod Whether self lending pair pod is used
/// @param _config Configuration parameters for the leverage operation
function _addLeveragePreCallback(
address _sender,
uint256 _positionId,
address _pod,
uint256 _pTknAmt,
uint256 _pairedLpDesired,
uint256 _userProvidedDebtAmt,
bool _hasSelfLendingPairPod,
bytes memory _config
) internal {
if (_positionId == 0) {
_positionId = _initializePosition(_pod, _sender, _hasSelfLendingPairPod);
} else {
address _owner = positionNFT.ownerOf(_positionId);
require(
_owner == _sender || positionNFT.getApproved(_positionId) == _sender
|| positionNFT.isApprovedForAll(_owner, _sender),
"A3"
);
_pod = positionProps[_positionId].pod;
}
if (_userProvidedDebtAmt > 0) {
IERC20(_getBorrowTknForPosition(_positionId)).safeTransferFrom(_sender, address(this), _userProvidedDebtAmt);
}
// if additional fees required for flash source, handle that here
_processExtraFlashLoanPayment(_positionId, _sender);
if (_pairedLpDesired > _userProvidedDebtAmt) {
IFlashLoanSource(_getFlashSource(_positionId)).flash(
_getBorrowTknForPosition(_positionId),
_pairedLpDesired - _userProvidedDebtAmt,
address(this),
_getFlashDataAddLeverage(_positionId, _sender, _pTknAmt, _pairedLpDesired, _config)
);
} else {
_callback(
abi.encode(
IFlashLoanSource.FlashData(
address(this),
_getBorrowTknForPosition(_positionId),
0,
_getFlashDataAddLeverage(_positionId, _sender, _pTknAmt, _pairedLpDesired, _config),
0
)
)
);
}
}
/// @notice Internal function to handle post-callback logic for adding leverage
/// @param _data Encoded flash loan data containing position and configuration information
/// @return _ptknRefundAmt Amount of pod tokens to refund to the user
/// @dev Processes LP addition, staking, collateral deposit, and flash loan repayment
function _addLeveragePostCallback(bytes memory _data) internal returns (uint256 _ptknRefundAmt) {
IFlashLoanSource.FlashData memory _d = abi.decode(_data, (IFlashLoanSource.FlashData));
(LeverageFlashProps memory _props,) = abi.decode(_d.data, (LeverageFlashProps, bytes));
(uint256 _overrideBorrowAmt,,) = abi.decode(_props.config, (uint256, uint256, uint256));
address _pod = positionProps[_props.positionId].pod;
uint256 _borrowTknAmtToLp = _props.pairedLpDesired;
// if there's an open fee send debt/borrow token to protocol
if (openFeePerc > 0) {
uint256 _openTotalFees = (_borrowTknAmtToLp * openFeePerc) / PRECISION;
_openTotalFees = _processFees(_pod, _d.token, _openTotalFees, true);
_borrowTknAmtToLp -= _openTotalFees;
}
(uint256 _pTknAmtUsed,, uint256 _pairedLeftover) = _lpAndStakeInPod(_d.token, _borrowTknAmtToLp, _props);
_ptknRefundAmt = _props.pTknAmt - _pTknAmtUsed;
uint256 _aspTknCollateralBal =
_spTknToAspTkn(IDecentralizedIndex(_pod).lpStakingPool(), _pairedLeftover, _props);
uint256 _flashPaybackAmt = _d.amount + _d.fee;
uint256 _borrowAmt = _overrideBorrowAmt > _flashPaybackAmt ? _overrideBorrowAmt : _flashPaybackAmt;
address _aspTkn = _getAspTkn(_props.positionId);
IERC20(_aspTkn).safeTransfer(positionProps[_props.positionId].custodian, _aspTknCollateralBal);
LeveragePositionCustodian(positionProps[_props.positionId].custodian).borrowAsset(
positionProps[_props.positionId].lendingPair, _borrowAmt, _aspTknCollateralBal, address(this)
);
// pay back flash loan and send remaining to borrower
if (_flashPaybackAmt > 0) {
IERC20(_d.token).safeTransfer(
IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _flashPaybackAmt
);
}
uint256 _remaining = IERC20(_d.token).balanceOf(address(this));
if (_remaining != 0) {
IERC20(_d.token).safeTransfer(positionNFT.ownerOf(_props.positionId), _remaining);
}
emit AddLeverage(_props.positionId, _props.owner, _pTknAmtUsed, _aspTknCollateralBal, _borrowAmt);
}
/// @notice Internal function to handle post-callback logic for removing leverage
/// @param _userData Encoded flash loan data containing position and removal configuration
/// @return _podAmtRemaining Amount of pod tokens remaining after removal
/// @return _borrowAmtRemaining Amount of borrow tokens remaining after flash loan repayment
/// @dev Processes collateral removal, LP unstaking, and flash loan repayment
function _removeLeveragePostCallback(bytes memory _userData)
internal
returns (uint256 _podAmtRemaining, uint256 _borrowAmtRemaining)
{
IFlashLoanSource.FlashData memory _d = abi.decode(_userData, (IFlashLoanSource.FlashData));
(LeverageFlashProps memory _props, bytes memory _additionalInfo) =
abi.decode(_d.data, (LeverageFlashProps, bytes));
(uint256 _borrowSharesToRepay, bytes memory _removeLevConfig) = abi.decode(_additionalInfo, (uint256, bytes));
(uint256 _collateralAssetRemoveAmt,,,, uint256 _userProvidedDebtAmt) =
abi.decode(_removeLevConfig, (uint256, uint256, uint256, uint256, uint256));
if (_userProvidedDebtAmt > 0) {
IERC20(_getBorrowTknForPosition(_props.positionId)).safeTransferFrom(
_props.sender, address(this), _userProvidedDebtAmt
);
}
LeveragePositionProps memory _posProps = positionProps[_props.positionId];
// allowance increases for borrowAmt prior to flash loaning asset
IFraxlendPair(_posProps.lendingPair).repayAsset(_borrowSharesToRepay, _posProps.custodian);
LeveragePositionCustodian(_posProps.custodian).removeCollateral(
_posProps.lendingPair, _collateralAssetRemoveAmt, address(this)
);
(uint256 _podAmtReceived, uint256 _pairedAmtReceived) =
_unstakeAndRemoveLP(_props.positionId, _posProps.pod, _collateralAssetRemoveAmt, _removeLevConfig);
_podAmtRemaining = _podAmtReceived;
// redeem borrow asset from lending pair for self lending positions
if (positionProps[_props.positionId].isSelfLending) {
// unwrap from self lending pod for lending pair asset
if (_posProps.hasSelfLendingPairPod) {
_pairedAmtReceived =
_debondFromSelfLendingPod(IDecentralizedIndex(_posProps.pod).PAIRED_LP_TOKEN(), _pairedAmtReceived);
}
IFraxlendPair(_posProps.lendingPair).redeem(_pairedAmtReceived, address(this), address(this));
_pairedAmtReceived = IERC20(_d.token).balanceOf(address(this));
}
// pay back flash loan and send remaining to borrower
uint256 _repayAmount = _d.amount + _d.fee;
if (_repayAmount > _pairedAmtReceived) {
uint256 _borrowAmtAcquired;
(_podAmtRemaining, _borrowAmtAcquired) = _acquireBorrowTokenForRepayment(
_props, _posProps.pod, _d.token, _repayAmount - _pairedAmtReceived, _podAmtReceived, _removeLevConfig
);
_pairedAmtReceived += _borrowAmtAcquired;
}
require(_pairedAmtReceived >= _repayAmount, "BAR");
if (_repayAmount > 0) {
IERC20(_d.token).safeTransfer(IFlashLoanSource(_getFlashSource(_props.positionId)).source(), _repayAmount);
}
_borrowAmtRemaining = _pairedAmtReceived - _repayAmount;
emit RemoveLeverage(_props.positionId, _props.owner, _collateralAssetRemoveAmt);
}
/// @notice Internal function to debond tokens from a self-lending pod
/// @param _pod The pod address to debond from
/// @param _amount The amount of pod tokens to debond
/// @return _amtOut The amount of underlying tokens received after debonding
/// @dev Debonds 100% of the specified amount to the first underlying asset
function _debondFromSelfLendingPod(address _pod, uint256 _amount) internal returns (uint256 _amtOut) {
IDecentralizedIndex.IndexAssetInfo[] memory _podAssets = IDecentralizedIndex(_pod).getAllAssets();
address[] memory _tokens = new address[](1);
uint8[] memory _percentages = new uint8[](1);
_tokens[0] = _podAssets[0].token;
_percentages[0] = 100;
IDecentralizedIndex(_pod).debond(_amount, _tokens, _percentages);
_amtOut = IERC20(_tokens[0]).balanceOf(address(this));
}
/// @notice Internal function to acquire borrow tokens for flash loan repayment by swapping pod tokens
/// @param _props Leverage flash properties containing position information
/// @param _pod The pod address to swap tokens from
/// @param _borrowToken The borrow token address needed for repayment
/// @param _borrowNeeded The amount of borrow tokens needed
/// @param _podAmtReceived The amount of pod tokens available for swapping
/// @param _removeLevConf Configuration data for leverage removal
/// @return _podAmtRemaining Amount of pod tokens remaining after swap
/// @return _borrowAmtReceived Amount of borrow tokens acquired from swap
/// @dev Handles both self-lending and regular lending scenarios
function _acquireBorrowTokenForRepayment(
LeverageFlashProps memory _props,
address _pod,
address _borrowToken,
uint256 _borrowNeeded,
uint256 _podAmtReceived,
bytes memory _removeLevConf
) internal returns (uint256 _podAmtRemaining, uint256 _borrowAmtReceived) {
_podAmtRemaining = _podAmtReceived;
uint256 _borrowAmtNeededToSwap = _borrowNeeded;
(,,, uint256 _podPairedLiquidityPrice18,) =
abi.decode(_removeLevConf, (uint256, uint256, uint256, uint256, uint256));
// sell pod token into LP for enough borrow token to get enough to repay
// if self-lending swap for lending pair then redeem for borrow token
if (_borrowAmtNeededToSwap > 0) {
uint256 _borrowAmtFromSwap;
if (positionProps[_props.positionId].isSelfLending) {
address _lendingPair = positionProps[_props.positionId].lendingPair;
(_podAmtRemaining,) = _swapPodForBorrowToken(
_pod,
_lendingPair,
_podAmtReceived,
IFraxlendPair(_lendingPair).convertToShares(_borrowAmtNeededToSwap),
_podPairedLiquidityPrice18
);
_borrowAmtFromSwap = IFraxlendPair(_lendingPair).redeem(
IERC20(_lendingPair).balanceOf(address(this)), address(this), address(this)
);
} else {
(_podAmtRemaining, _borrowAmtFromSwap) = _swapPodForBorrowToken(
_pod, _borrowToken, _podAmtReceived, _borrowAmtNeededToSwap, _podPairedLiquidityPrice18
);
}
_borrowAmtReceived += _borrowAmtFromSwap;
}
}
/// @notice Internal function to swap pod tokens for borrow tokens using DEX adapter
/// @param _pod The pod token address to swap from
/// @param _targetToken The target token address to swap to
/// @param _podAmt The amount of pod tokens available for swapping
/// @param _targetNeededAmt The amount of target tokens needed
/// @param _podPairedLiquidityPrice18 Price of pod LP with 18 decimal precision
/// @return _podRemainingAmt Amount of pod tokens remaining after swap
/// @return _targetReceivedAmt Amount of target tokens received from swap
/// @dev Uses price information to optimize swap amounts and includes slippage protection
function _swapPodForBorrowToken(
address _pod,
address _targetToken,
uint256 _podAmt,
uint256 _targetNeededAmt,
uint256 _podPairedLiquidityPrice18
) internal returns (uint256 _podRemainingAmt, uint256 _targetReceivedAmt) {
IDexAdapter _dexAdapter = IDecentralizedIndex(_pod).DEX_HANDLER();
uint256 _podBalBefore = IERC20(_pod).balanceOf(address(this));
uint256 _podAmountIn = _podAmt;
if (_podPairedLiquidityPrice18 > 0) {
address _t1 = _pod < _targetToken ? _targetToken : _pod;
uint256 _podAmountInExact = _targetToken == _t1
? (_targetNeededAmt * 10 ** 18) / _podPairedLiquidityPrice18
: (_targetNeededAmt * _podPairedLiquidityPrice18) / 10 ** 18;
_podAmountIn = (_podAmountInExact * 105) / 100; // add 5% to account for slippage/price impact
_podAmountIn = _podAmountIn > _podAmt ? _podAmt : _podAmountIn;
}
IERC20(_pod).safeIncreaseAllowance(address(_dexAdapter), _podAmountIn);
_targetReceivedAmt = _dexAdapter.swapV2Single(_pod, _targetToken, _podAmountIn, _targetNeededAmt, address(this));
_podRemainingAmt = _podAmt - (_podBalBefore - IERC20(_pod).balanceOf(address(this)));
}
/// @notice Internal function to add liquidity to a pod and stake the LP tokens
/// @param _borrowToken The borrowed token address used for LP
/// @param _borrowAmt The amount of borrowed tokens to use for LP
/// @param _props Leverage flash properties containing position and configuration data
/// @return _pTknAmtUsed Amount of pod tokens used in the LP operation
/// @return _pairedLpUsed Amount of paired LP tokens used in the operation
/// @return _pairedLpLeftover Amount of paired LP tokens remaining after operation
/// @dev Processes borrowed tokens into appropriate paired tokens and adds LP with staking
function _lpAndStakeInPod(address _borrowToken, uint256 _borrowAmt, LeverageFlashProps memory _props)
internal
returns (uint256 _pTknAmtUsed, uint256 _pairedLpUsed, uint256 _pairedLpLeftover)
{
(, uint256 _slippage, uint256 _deadline) = abi.decode(_props.config, (uint256, uint256, uint256));
(address _pairedLpForPod, uint256 _pairedLpAmt) = _processAndGetPairedTknAndAmt(
_props.positionId, _borrowToken, _borrowAmt, positionProps[_props.positionId].hasSelfLendingPairPod
);
uint256 _podBalBefore = IERC20(positionProps[_props.positionId].pod).balanceOf(address(this));
uint256 _pairedLpBalBefore = IERC20(_pairedLpForPod).balanceOf(address(this));
IERC20(positionProps[_props.positionId].pod).safeIncreaseAllowance(address(indexUtils), _props.pTknAmt);
IERC20(_pairedLpForPod).safeIncreaseAllowance(address(indexUtils), _pairedLpAmt);
indexUtils.addLPAndStake(
IDecentralizedIndex(positionProps[_props.positionId].pod),
_props.pTknAmt,
_pairedLpForPod,
_pairedLpAmt,
0, // is not used so can use max slippage
_slippage,
_deadline
);
_pTknAmtUsed = _podBalBefore - IERC20(positionProps[_props.positionId].pod).balanceOf(address(this));
_pairedLpUsed = _pairedLpBalBefore - IERC20(_pairedLpForPod).balanceOf(address(this));
_pairedLpLeftover = _pairedLpBalBefore - _pairedLpUsed;
}
/// @notice Internal function to convert staking pool tokens to ASP tokens and handle remaining paired assets
/// @param _spTkn The staking pool token address
/// @param _pairedRemainingAmt Amount of paired tokens remaining after LP operations
/// @param _props Leverage flash properties containing position information
/// @return _newAspTkns Amount of new ASP tokens created from staking pool tokens
/// @dev Deposits staking tokens into ASP vault and handles self-lending pod redemptions
function _spTknToAspTkn(address _spTkn, uint256 _pairedRemainingAmt, LeverageFlashProps memory _props)
internal
returns (uint256 _newAspTkns)
{
address _aspTkn = _getAspTkn(_props.positionId);
uint256 _stakingBal = IERC20(_spTkn).balanceOf(address(this));
IERC20(_spTkn).safeIncreaseAllowance(_aspTkn, _stakingBal);
_newAspTkns = IERC4626(_aspTkn).deposit(_stakingBal, address(this));
// for self lending pods redeem any extra paired LP asset back into main asset
if (positionProps[_props.positionId].isSelfLending && _pairedRemainingAmt > 0) {
if (positionProps[_props.positionId].hasSelfLendingPairPod) {
address[] memory _noop1;
uint8[] memory _noop2;
IDecentralizedIndex(IDecentralizedIndex(positionProps[_props.positionId].pod).PAIRED_LP_TOKEN()).debond(
_pairedRemainingAmt, _noop1, _noop2
);
_pairedRemainingAmt = IERC20(positionProps[_props.positionId].lendingPair).balanceOf(address(this));
}
IFraxlendPair(positionProps[_props.positionId].lendingPair).redeem(
_pairedRemainingAmt, address(this), address(this)
);
}
}
/// @notice Internal function to validate and adjust remove leverage configuration based on borrow amount
/// @param _borrowAmt The actual borrow amount to be repaid
/// @param _remLevConfig The original remove leverage configuration
/// @return _finalUserProvidedDebtAmt The adjusted user provided debt amount
/// @return _finalRemLevConfig The adjusted remove leverage configuration
/// @dev Ensures user provided debt amount doesn't exceed the actual borrow amount
function _checkAndResetRemoveLeverageConfigFromBorrowAmt(uint256 _borrowAmt, bytes memory _remLevConfig)
internal
pure
returns (uint256 _finalUserProvidedDebtAmt, bytes memory _finalRemLevConfig)
{
(uint256 _1, uint256 _2, uint256 _3, uint256 _4, uint256 _userProvidedDebtAmt) =
abi.decode(_remLevConfig, (uint256, uint256, uint256, uint256, uint256));
_finalUserProvidedDebtAmt = _userProvidedDebtAmt;
if (_userProvidedDebtAmt > _borrowAmt) {
_finalUserProvidedDebtAmt = _borrowAmt;
}
_finalRemLevConfig = abi.encode(_1, _2, _3, _4, _finalUserProvidedDebtAmt);
}
/// @notice Internal function to process borrowed tokens and convert them to appropriate paired tokens for LP
/// @param _positionId The position ID to get lending pair information
/// @param _borrowedTkn The borrowed token address
/// @param _borrowedAmt The amount of borrowed tokens
/// @param _hasSelfLendingPairPod Whether the self lending pair pod is used
/// @return _finalPairedTkn The final paired token address for LP operations
/// @return _finalPairedAmt The final amount of paired tokens for LP operations
/// @dev Handles conversion for self-lending scenarios including podded lending pairs
function _processAndGetPairedTknAndAmt(
uint256 _positionId,
address _borrowedTkn,
uint256 _borrowedAmt,
bool _hasSelfLendingPairPod
) internal returns (address _finalPairedTkn, uint256 _finalPairedAmt) {
_finalPairedTkn = _borrowedTkn;
_finalPairedAmt = _borrowedAmt;
address _lendingPair = positionProps[_positionId].lendingPair;
if (positionProps[_positionId].isSelfLending) {
_finalPairedTkn = _lendingPair;
IERC20(_borrowedTkn).safeIncreaseAllowance(_lendingPair, _finalPairedAmt);
_finalPairedAmt = IFraxlendPair(_lendingPair).deposit(_finalPairedAmt, address(this));
// self lending+podded
if (_hasSelfLendingPairPod) {
_finalPairedTkn = IDecentralizedIndex(positionProps[_positionId].pod).PAIRED_LP_TOKEN();
IERC20(_lendingPair).safeIncreaseAllowance(_finalPairedTkn, _finalPairedAmt);
IDecentralizedIndex(_finalPairedTkn).bond(_lendingPair, _finalPairedAmt, 0);
_finalPairedAmt = IERC20(_finalPairedTkn).balanceOf(address(this));
}
}
}
/// @notice Internal function to unstake LP tokens and remove liquidity from a pod
/// @param _positionId The position ID to get ASP token information
/// @param _pod The pod address to remove LP from
/// @param _collateralAssetRemoveAmt Amount of collateral (ASP tokens) to remove
/// @param _remLevConf Remove leverage configuration containing slippage parameters
/// @return _podAmtReceived Amount of pod tokens received from LP removal
/// @return _pairedAmtReceived Amount of paired tokens received from LP removal
/// @dev Redeems ASP tokens for staking tokens, then unstakes and removes LP
function _unstakeAndRemoveLP(
uint256 _positionId,
address _pod,
uint256 _collateralAssetRemoveAmt,
bytes memory _remLevConf
) internal returns (uint256 _podAmtReceived, uint256 _pairedAmtReceived) {
(, uint256 _podAmtMin, uint256 _pairedAssetAmtMin,,) =
abi.decode(_remLevConf, (uint256, uint256, uint256, uint256, uint256));
address _spTkn = IDecentralizedIndex(_pod).lpStakingPool();
address _pairedLpToken = IDecentralizedIndex(_pod).PAIRED_LP_TOKEN();
uint256 _podAmtBefore = IERC20(_pod).balanceOf(address(this));
uint256 _pairedTokenAmtBefore = IERC20(_pairedLpToken).balanceOf(address(this));
uint256 _spTknAmtReceived =
IERC4626(_getAspTkn(_positionId)).redeem(_collateralAssetRemoveAmt, address(this), address(this));
IERC20(_spTkn).safeIncreaseAllowance(address(indexUtils), _spTknAmtReceived);
indexUtils.unstakeAndRemoveLP(
IDecentralizedIndex(_pod), _spTknAmtReceived, _podAmtMin, _pairedAssetAmtMin, block.timestamp
);
_podAmtReceived = IERC20(_pod).balanceOf(address(this)) - _podAmtBefore;
_pairedAmtReceived = IERC20(_pairedLpToken).balanceOf(address(this)) - _pairedTokenAmtBefore;
}
/// @notice Internal function to bond underlying tokens to a pod to mint pod tokens
/// @param _user The user address to transfer underlying tokens from
/// @param _pod The pod address to bond tokens to
/// @param _tknAmt The amount of underlying tokens to bond
/// @param _amtPtknMintMin The minimum amount of pod tokens expected to be minted
/// @dev Transfers underlying tokens from user and bonds them to the pod
function _bondToPod(address _user, address _pod, uint256 _tknAmt, uint256 _amtPtknMintMin) internal {
IDecentralizedIndex.IndexAssetInfo[] memory _podAssets = IDecentralizedIndex(_pod).getAllAssets();
IERC20 _tkn = IERC20(_podAssets[0].token);
uint256 _tknBalBefore = _tkn.balanceOf(address(this));
_tkn.safeTransferFrom(_user, address(this), _tknAmt);
uint256 _pTknBalBefore = IERC20(_pod).balanceOf(address(this));
_tkn.safeIncreaseAllowance(_pod, _tkn.balanceOf(address(this)) - _tknBalBefore);
IDecentralizedIndex(_pod).bond(address(_tkn), _tkn.balanceOf(address(this)) - _tknBalBefore, _amtPtknMintMin);
IERC20(_pod).balanceOf(address(this)) - _pTknBalBefore;
}
/// @notice Internal view function to get the borrow token address for a position
/// @param _positionId The position ID to get borrow token for
/// @return The address of the borrow token (lending pair asset)
function _getBorrowTknForPosition(uint256 _positionId) internal view returns (address) {
return IFraxlendPair(positionProps[_positionId].lendingPair).asset();
}
/// @notice Internal view function to get the flash loan source address for a position
/// @param _positionId The position ID to get flash source for
/// @return The address of the flash loan source for the position's borrow token
function _getFlashSource(uint256 _positionId) internal view returns (address) {
return flashSource[_getBorrowTknForPosition(_positionId)];
}
/// @notice Internal view function to get the ASP token address for a position
/// @param _positionId The position ID to get ASP token for
/// @return The address of the ASP token (lending pair collateral contract)
function _getAspTkn(uint256 _positionId) internal view returns (address) {
return IFraxlendPair(positionProps[_positionId].lendingPair).collateralContract();
}
/// @notice Internal view function to encode flash loan data for adding leverage
/// @param _positionId The position ID for the leverage operation
/// @param _sender The address initiating the leverage addition
/// @param _pTknAmt Amount of pod tokens to use
/// @param _pairedLpDesired Total amount of paired LP tokens desired
/// @param _config Configuration parameters for the leverage operation
/// @return Encoded flash loan data for add leverage operation
function _getFlashDataAddLeverage(
uint256 _positionId,
address _sender,
uint256 _pTknAmt,
uint256 _pairedLpDesired,
bytes memory _config
) internal view returns (bytes memory) {
return abi.encode(
LeverageFlashProps({
method: FlashCallbackMethod.ADD,
positionId: _positionId,
owner: positionNFT.ownerOf(_positionId),
sender: _sender,
pTknAmt: _pTknAmt,
pairedLpDesired: _pairedLpDesired,
config: _config
}),
""
);
}
/// @notice Processes fees to both any partner configured or insurance funds
function _processFees(address _pod, address _tkn, uint256 _totalFees, bool _isOpening)
internal
returns (uint256 _totalFeesProcessed)
{
if (_totalFees > 0 && feeProcessor != address(0)) {
IERC20(_tkn).safeTransfer(feeProcessor, _totalFees);
ILeverageFeeProcessor(feeProcessor).processFees(_pod, _tkn, _totalFees, feeReceiver, _isOpening);
_totalFeesProcessed = _totalFees;
}
}
/// @notice Sets the position NFT contract address
/// @param _posNFT The new position NFT contract address
/// @dev Only callable by the contract owner
function setPositionNFT(ILeveragePositions _posNFT) external onlyOwner {
address _oldPosNFT = address(positionNFT);
positionNFT = _posNFT;
emit SetPositionsNFT(_oldPosNFT, address(_posNFT));
}
/// @notice Sets the IndexUtils contract address
/// @param _utils The new IndexUtils contract address
/// @dev Only callable by the contract owner
function setIndexUtils(IIndexUtils _utils) external onlyOwner {
address _old = address(indexUtils);
indexUtils = _utils;
emit SetIndexUtils(_old, address(_utils));
}
/// @notice Sets the fee receiver address
/// @param _receiver The new fee receiver address
/// @dev Only callable by the contract owner
function setFeeReceiver(address _receiver) external onlyOwner {
address _currentReceiver = feeReceiver;
feeReceiver = _receiver;
emit SetFeeReceiver(_currentReceiver, _receiver);
}
function setFeeProcessor(address _processor) external onlyOwner {
address _currentProcessor = feeProcessor;
feeProcessor = _processor;
emit SetFeeProcessor(_currentProcessor, _processor);
}
/// @notice Sets the opening fee percentage
/// @param _newFee The new opening fee percentage (max 2500 = 25%)
/// @dev Only callable by the contract owner, fee cannot exceed 25%
function setOpenFeePerc(uint16 _newFee) external onlyOwner {
require(_newFee <= 2500, "M");
uint16 _oldFee = openFeePerc;
openFeePerc = _newFee;
emit SetOpenFeePerc(_oldFee, _newFee);
}
/// @notice Sets the closing fee percentage
/// @param _newFee The new closing fee percentage (max 2500 = 25%)
/// @dev Only callable by the contract owner, fee cannot exceed 25%
function setCloseFeePerc(uint16 _newFee) external onlyOwner {
require(_newFee <= 2500, "M");
uint16 _oldFee = closeFeePerc;
closeFeePerc = _newFee;
emit SetCloseFeePerc(_oldFee, _newFee);
}
function setLeverageFactory(address _factory) external override onlyOwner {
leverageFactory = _factory;
emit SetLeverageFactory(_factory);
}
/// @notice Emergency function to rescue ERC20 tokens from the contract
/// @param _token The ERC20 token contract to rescue
/// @dev Only callable by the contract owner
function rescueTokens(IERC20 _token) external onlyOwner {
_token.safeTransfer(_msgSender(), _token.balanceOf(address(this)));
}
}
"
},
"node_modules/@openzeppelin/contracts/interfaces/IERC4626.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC4626.sol)
pragma solidity >=0.6.2;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
"
},
"node_modules/@openzeppelin/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);
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.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 t
Submitted on: 2025-10-09 19:03:09
Comments
Log in to comment.
No comments yet.