Diff
checker
टेक्स्ट
टेक्स्ट
छवियां
दस्तावेज़
Excel
फ़ोल्डर्स
Legal
Enterprise
डेस्कटॉप
मूल्य
साइन इन करें
Diffchecker डेस्कटॉप डाउनलोड करें
टेक्स्ट की तुलना करें
दो टेक्स्ट फ़ाइलों के बीच अंतर ढूंढें
उपकरण
इतिहास
प्रदर्शन
लेआउट
विभाजित
संयुक्त
रियल-टाइम एडिटर
रिक्त स्थान छिपाएँ
अपरिवर्तित संक्षिप्त करें
लाइन रैप बंद
परिवर्तन हाइलाइट करें
स्मार्ट
सिंटैक्स
कोई नहीं
दिखावट बदलें
प्रोसेसिंग
अनदेखा करें
टेक्स्ट बदलें
पहले अंतर पर जाएँ
इनपुट संपादित करें
Diffchecker Desktop
Diffchecker चलाने का सबसे सुरक्षित तरीका। Diffchecker Desktop ऐप पाएं: आपके diffs कभी आपके कंप्यूटर से बाहर नहीं जाते!
Desktop पाएं
LRTWithdrawalManager
बनाया गया
12 माह पहले
Diff कभी समाप्त नहीं होता
साफ़
निर्यात करें
शेयर करें
समझाएं
90 हटाए गए
लाइनें
कुल
हटाया गया
अक्षर
कुल
हटाया गया
इस सुविधा का उपयोग जारी रखने के लिए, अपग्रेड करें
Diff
checker
Pro
मूल्य देखें
434 लाइनें
सभी को कॉपी करें
221 जोड़े गए
लाइनें
कुल
जोड़ा गया
अक्षर
कुल
जोड़ा गया
इस सुविधा का उपयोग जारी रखने के लिए, अपग्रेड करें
Diff
checker
Pro
मूल्य देखें
564 लाइनें
सभी को कॉपी करें
// SPDX-License-Identifier: BUSL-1.1
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.27;
pragma solidity 0.8.27;
import { UtilLib } from "./utils/UtilLib.sol";
import { UtilLib } from "./utils/UtilLib.sol";
import { LRTConstants } from "./utils/LRTConstants.sol";
import { LRTConstants } from "./utils/LRTConstants.sol";
import { DoubleEndedQueue } from "./utils/DoubleEndedQueue.sol";
import { DoubleEndedQueue } from "./utils/DoubleEndedQueue.sol";
import { LRTConfigRoleChecker, ILRTConfig } from "./utils/LRTConfigRoleChecker.sol";
import { LRTConfigRoleChecker, ILRTConfig } from "./utils/LRTConfigRoleChecker.sol";
import { IRSETH } from "./interfaces/IRSETH.sol";
import { IRSETH } from "./interfaces/IRSETH.sol";
import { ILRTOracle } from "./interfaces/ILRTOracle.sol";
import { ILRTOracle } from "./interfaces/ILRTOracle.sol";
import { ILRTWithdrawalManager } from "./interfaces/ILRTWithdrawalManager.sol";
import { ILRTWithdrawalManager } from "./interfaces/ILRTWithdrawalManager.sol";
import { ILRTDepositPool } from "./interfaces/ILRTDepositPool.sol";
import { ILRTDepositPool } from "./interfaces/ILRTDepositPool.sol";
import { ILRTUnstakingVault } from "./interfaces/ILRTUnstakingVault.sol";
import { ILRTUnstakingVault } from "./interfaces/ILRTUnstakingVault.sol";
कॉपी
कॉपी हुआ
कॉपी
कॉपी हुआ
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
कॉपी
कॉपी हुआ
कॉपी
कॉपी हुआ
import {
ReentrancyGuardUpgradeable
} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {
ReentrancyGuardUpgradeable
} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/// @title LRTWithdrawalManager - Withdraw Manager Contract for rsETH => LSTs
/// @title LRTWithdrawalManager - Withdraw Manager Contract for rsETH => LSTs
/// @notice Handles LST asset withdraws
/// @notice Handles LST asset withdraws
contract LRTWithdrawalManager is
contract LRTWithdrawalManager is
ILRTWithdrawalManager,
ILRTWithdrawalManager,
LRTConfigRoleChecker,
LRTConfigRoleChecker,
PausableUpgradeable,
PausableUpgradeable,
ReentrancyGuardUpgradeable
ReentrancyGuardUpgradeable
{
{
using DoubleEndedQueue for DoubleEndedQueue.Uint256Deque;
using DoubleEndedQueue for DoubleEndedQueue.Uint256Deque;
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
mapping(address asset => uint256) public minRsEthAmountToWithdraw;
mapping(address asset => uint256) public minRsEthAmountToWithdraw;
uint256 public withdrawalDelayBlocks;
uint256 public withdrawalDelayBlocks;
// Next available nonce for withdrawal requests per asset, indicating total requests made.
// Next available nonce for withdrawal requests per asset, indicating total requests made.
mapping(address asset => uint256 nonce) public nextUnusedNonce;
mapping(address asset => uint256 nonce) public nextUnusedNonce;
// Next nonce for which a withdrawal request remains locked.
// Next nonce for which a withdrawal request remains locked.
mapping(address asset => uint256 requestNonce) public nextLockedNonce;
mapping(address asset => uint256 requestNonce) public nextLockedNonce;
// Mapping from a unique request identifier to its corresponding withdrawal request
// Mapping from a unique request identifier to its corresponding withdrawal request
mapping(bytes32 requestId => WithdrawalRequest) public withdrawalRequests;
mapping(bytes32 requestId => WithdrawalRequest) public withdrawalRequests;
कॉपी
कॉपी हुआ
कॉपी
कॉपी हुआ
// Maps each asset to user addresses, pointing to an order
t
list of their withdrawal request nonces.
// Maps each asset to user addresses, pointing to an order
ed
list of their withdrawal request nonces.
// Utilizes a double-ended queue for efficient management and removal of initial requests.
// Utilizes a double-ended queue for efficient management and removal of initial requests.
mapping(address asset => mapping(address user => DoubleEndedQueue.Uint256Deque requestNonces)) public
mapping(address asset => mapping(address user => DoubleEndedQueue.Uint256Deque requestNonces)) public
userAssociatedNonces;
userAssociatedNonces;
कॉपी
कॉपी हुआ
कॉपी
कॉपी हुआ
// Asset amount commit
ed to be withdrawn by users.
// Asset amount commit
t
ed to be withdrawn by users.
mapping(address asset => uint256 amount) public assetsCommitted;
mapping(address asset => uint256 amount) public assetsCommitted;
कॉपी
कॉपी हुआ
कॉपी
कॉपी हुआ
mapping(address asset => bool) public isInstantWithdrawalEnabled;
uint256 public instantWithdrawalFee; // Fee in basis points (1 = 0.01%)
mapping(address asset => uint256) public unlockedWithdrawalsCount;
modifier onlySupportedStrategy(address asset) {
modifier onlySupportedStrategy(address asset) {
if (asset != LRTConstants.ETH_TOKEN && lrtConfig.assetStrategy(asset) == address(0)) {
if (asset != LRTConstants.ETH_TOKEN && lrtConfig.assetStrategy(asset) == address(0)) {
revert StrategyNotSupported();
revert StrategyNotSupported();
}
}
_;
_;
}
}
कॉपी
कॉपी हुआ
कॉपी
कॉपी हुआ
modifier onlyInstantWithdrawalAllowed(address asset) {
if (!isInstantWithdrawalEnabled[asset]) revert InstantWithdrawalNotEnabled();
_;
}
modifier onlyAfterInitialize2() {
if (!isInitialized2()) revert NotInitialized();
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
constructor() {
_disableInitializers();
_disableInitializers();
}
}
/// @notice Initializes the contract
/// @notice Initializes the contract
/// @param lrtConfigAddr LRT config address
/// @param lrtConfigAddr LRT config address
function initialize(address lrtConfigAddr) external initializer {
function initialize(address lrtConfigAddr) external initializer {
UtilLib.checkNonZeroAddress(lrtConfigAddr);
UtilLib.checkNonZeroAddress(lrtConfigAddr);
__Pausable_init();
__Pausable_init();
__ReentrancyGuard_init();
__ReentrancyGuard_init();
withdrawalDelayBlocks = 8 days / 12 seconds;
withdrawalDelayBlocks = 8 days / 12 seconds;
lrtConfig = ILRTConfig(lrtConfigAddr);
lrtConfig = ILRTConfig(lrtConfigAddr);
emit UpdatedLRTConfig(lrtConfigAddr);
emit UpdatedLRTConfig(lrtConfigAddr);
}
}
कॉपी
कॉपी हुआ
कॉपी
कॉपी हुआ
/// @notice Initializes unlocked withdrawals count for ETHx, STETH and ETH
/// @param unlockedWithdrawalsCountETHx Initial unlocked withdrawals count for ETHx
/// @param unlockedWithdrawalsCountSTETH Initial unlocked withdrawals count for STETH
/// @param unlockedWithdrawalsCountETH Initial unlocked withdrawals count for ETH
function initialize2(
uint256 unlockedWithdrawalsCountETHx,
uint256 unlockedWithdrawalsCountSTETH,
uint256 unlockedWithdrawalsCountETH
)
external
reinitializer(2)
onlyRole(LRTConstants.UNLOCKED_WITHDRAWAL_INITIALIZER)
{
unlockedWithdrawalsCount[lrtConfig.getLSTToken(LRTConstants.ST_ETH_TOKEN)] = unlockedWithdrawalsCountSTETH;
unlockedWithdrawalsCount[lrtConfig.getLSTToken(LRTConstants.ETHX_TOKEN)] = unlockedWithdrawalsCountETHx;
unlockedWithdrawalsCount[LRTConstants.ETH_TOKEN] = unlockedWithdrawalsCountETH;
}
/*//////////////////////////////////////////////////////////////
/*//////////////////////////////////////////////////////////////
receive functions
receive functions
//////////////////////////////////////////////////////////////*/
//////////////////////////////////////////////////////////////*/
receive() external payable { }
receive() external payable { }
/// @dev receive from LRTUnstakingVault
/// @dev receive from LRTUnstakingVault
function receiveFromLRTUnstakingVault() external payable { }
function receiveFromLRTUnstakingVault() external payable { }
/*//////////////////////////////////////////////////////////////
/*//////////////////////////////////////////////////////////////
User Withdrawal functions
User Withdrawal functions
//////////////////////////////////////////////////////////////*/
//////////////////////////////////////////////////////////////*/
/// @notice Initiates a withdrawal request for converting rsETH to a specified LST.
/// @notice Initiates a withdrawal request for converting rsETH to a specified LST.
/// @param asset The LST address the user wants to receive.
/// @param asset The LST address the user wants to receive.
/// @param rsETHUnstaked The amount of rsETH the user wishes to unstake.
/// @param rsETHUnstaked The amount of rsETH the user wishes to unstake.
/// @dev This function is only callable by the user and is used to initiate a withdrawal request for a specific
/// @dev This function is only callable by the user and is used to initiate a withdrawal request for a specific
/// asset. Will be finalised by calling `completeWithdrawal` after the manager unlocked the request and the delay
/// asset. Will be finalised by calling `completeWithdrawal` after the manager unlocked the request and the delay
/// has past. There is an edge case were the user withdraws last underlying asset and that asset gets slashed.
/// has past. There is an edge case were the user withdraws last underlying asset and that asset gets slashed.
function initiateWithdrawal(
function initiateWithdrawal(
address asset,
address asset,
uint256 rsETHUnstaked,
uint256 rsETHUnstaked,
string calldata referralId
string calldata referralId
सेव किए गए Diffs
ऑरिजनल टेक्स्ट
फ़ाइल खोलें
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.27; import { UtilLib } from "./utils/UtilLib.sol"; import { LRTConstants } from "./utils/LRTConstants.sol"; import { DoubleEndedQueue } from "./utils/DoubleEndedQueue.sol"; import { LRTConfigRoleChecker, ILRTConfig } from "./utils/LRTConfigRoleChecker.sol"; import { IRSETH } from "./interfaces/IRSETH.sol"; import { ILRTOracle } from "./interfaces/ILRTOracle.sol"; import { ILRTWithdrawalManager } from "./interfaces/ILRTWithdrawalManager.sol"; import { ILRTDepositPool } from "./interfaces/ILRTDepositPool.sol"; import { ILRTUnstakingVault } from "./interfaces/ILRTUnstakingVault.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title LRTWithdrawalManager - Withdraw Manager Contract for rsETH => LSTs /// @notice Handles LST asset withdraws contract LRTWithdrawalManager is ILRTWithdrawalManager, LRTConfigRoleChecker, PausableUpgradeable, ReentrancyGuardUpgradeable { using DoubleEndedQueue for DoubleEndedQueue.Uint256Deque; using SafeERC20 for IERC20; mapping(address asset => uint256) public minRsEthAmountToWithdraw; uint256 public withdrawalDelayBlocks; // Next available nonce for withdrawal requests per asset, indicating total requests made. mapping(address asset => uint256 nonce) public nextUnusedNonce; // Next nonce for which a withdrawal request remains locked. mapping(address asset => uint256 requestNonce) public nextLockedNonce; // Mapping from a unique request identifier to its corresponding withdrawal request mapping(bytes32 requestId => WithdrawalRequest) public withdrawalRequests; // Maps each asset to user addresses, pointing to an ordert list of their withdrawal request nonces. // Utilizes a double-ended queue for efficient management and removal of initial requests. mapping(address asset => mapping(address user => DoubleEndedQueue.Uint256Deque requestNonces)) public userAssociatedNonces; // Asset amount commited to be withdrawn by users. mapping(address asset => uint256 amount) public assetsCommitted; modifier onlySupportedStrategy(address asset) { if (asset != LRTConstants.ETH_TOKEN && lrtConfig.assetStrategy(asset) == address(0)) { revert StrategyNotSupported(); } _; } /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /// @notice Initializes the contract /// @param lrtConfigAddr LRT config address function initialize(address lrtConfigAddr) external initializer { UtilLib.checkNonZeroAddress(lrtConfigAddr); __Pausable_init(); __ReentrancyGuard_init(); withdrawalDelayBlocks = 8 days / 12 seconds; lrtConfig = ILRTConfig(lrtConfigAddr); emit UpdatedLRTConfig(lrtConfigAddr); } /*////////////////////////////////////////////////////////////// receive functions //////////////////////////////////////////////////////////////*/ receive() external payable { } /// @dev receive from LRTUnstakingVault function receiveFromLRTUnstakingVault() external payable { } /*////////////////////////////////////////////////////////////// User Withdrawal functions //////////////////////////////////////////////////////////////*/ /// @notice Initiates a withdrawal request for converting rsETH to a specified LST. /// @param asset The LST address the user wants to receive. /// @param rsETHUnstaked The amount of rsETH the user wishes to unstake. /// @dev This function is only callable by the user and is used to initiate a withdrawal request for a specific /// asset. Will be finalised by calling `completeWithdrawal` after the manager unlocked the request and the delay /// has past. There is an edge case were the user withdraws last underlying asset and that asset gets slashed. function initiateWithdrawal( address asset, uint256 rsETHUnstaked, string calldata referralId ) external override nonReentrant whenNotPaused onlySupportedAsset(asset) onlySupportedStrategy(asset) { if (rsETHUnstaked == 0 || rsETHUnstaked < minRsEthAmountToWithdraw[asset]) revert InvalidAmountToWithdraw(); IERC20(lrtConfig.rsETH()).safeTransferFrom(msg.sender, address(this), rsETHUnstaked); uint256 expectedAssetAmount = getExpectedAssetAmount(asset, rsETHUnstaked); // Ensure the withdrawal does not exceed the available shares. if (expectedAssetAmount > getAvailableAssetAmount(asset)) revert ExceedAmountToWithdraw(); // preventing over-withdrawal. assetsCommitted[asset] += expectedAssetAmount; _addUserWithdrawalRequest(asset, rsETHUnstaked, expectedAssetAmount); emit ReferralIdEmitted(referralId); } /// @notice Completes a user's withdrawal process by transferring the ETH/LST amount corresponding to the rsETH /// unstaked. /// @param asset The asset address the user wishes to withdraw. function completeWithdrawal(address asset, string calldata referralId) external nonReentrant whenNotPaused { // Retrieve and remove the oldest withdrawal request for the user. uint256 usersFirstWithdrawalRequestNonce = userAssociatedNonces[asset][msg.sender].popFront(); // Ensure the request is already unlocked. if (usersFirstWithdrawalRequestNonce >= nextLockedNonce[asset]) revert WithdrawalLocked(); bytes32 requestId = getRequestId(asset, usersFirstWithdrawalRequestNonce); WithdrawalRequest memory request = withdrawalRequests[requestId]; delete withdrawalRequests[requestId]; // Check that the withdrawal delay has passed since the request's initiation. if (block.number < request.withdrawalStartBlock + withdrawalDelayBlocks) revert WithdrawalDelayNotPassed(); if (asset == LRTConstants.ETH_TOKEN) { (bool sent,) = payable(msg.sender).call{ value: request.expectedAssetAmount }(""); if (!sent) revert EthTransferFailed(); } else { IERC20(asset).safeTransfer(msg.sender, request.expectedAssetAmount); } emit ReferralIdEmitted(referralId); emit AssetWithdrawalFinalized(msg.sender, asset, request.rsETHUnstaked, request.expectedAssetAmount); } /*////////////////////////////////////////////////////////////// operational functions //////////////////////////////////////////////////////////////*/ /// @notice Unlocks assets in the queue up to a specified limit. /// @param asset The address of the asset to unlock. /// @param firstExcludedIndex First withdrawal requests index that will not be considered for unlocking. /// @param minimumAssetPrice The minimum acceptable price for the asset. /// @param minimumRsEthPrice The minimum acceptable price for rsETH. function unlockQueue( address asset, uint256 firstExcludedIndex, uint256 minimumAssetPrice, uint256 minimumRsEthPrice, uint256 maximumAssetPrice, uint256 maximumRsEthPrice ) external nonReentrant onlySupportedAsset(asset) onlyLRTOperator returns (uint256 rsETHBurned, uint256 assetAmountUnlocked) { ILRTOracle lrtOracle = ILRTOracle(lrtConfig.getContract(LRTConstants.LRT_ORACLE)); ILRTUnstakingVault unstakingVault = ILRTUnstakingVault(lrtConfig.getContract(LRTConstants.LRT_UNSTAKING_VAULT)); UnlockParams memory params = _createUnlockParams(lrtOracle, unstakingVault, asset, firstExcludedIndex); _validatePrices( params.rsETHPrice, params.assetPrice, minimumRsEthPrice, maximumRsEthPrice, minimumAssetPrice, maximumAssetPrice ); if (params.totalAvailableAssets == 0) revert AmountMustBeGreaterThanZero(); // Updates and unlocks withdrawal requests up to a specified upper limit or until allocated assets are fully // utilized. (rsETHBurned, assetAmountUnlocked) = _unlockWithdrawalRequests( asset, params.totalAvailableAssets, params.rsETHPrice, params.assetPrice, firstExcludedIndex ); if (rsETHBurned != 0) IRSETH(lrtConfig.rsETH()).burnFrom(address(this), rsETHBurned); //Take the amount to distribute from vault unstakingVault.redeem(asset, assetAmountUnlocked); emit AssetUnlocked(asset, rsETHBurned, assetAmountUnlocked, params.rsETHPrice, params.assetPrice); } /*////////////////////////////////////////////////////////////// setters //////////////////////////////////////////////////////////////*/ /// @notice update min amount to withdraw /// @dev only callable by LRT admin /// @param asset Asset address /// @param minRsEthAmountToWithdraw_ Minimum amount to withdraw function setMinRsEthAmountToWithdraw(address asset, uint256 minRsEthAmountToWithdraw_) external onlyLRTAdmin { minRsEthAmountToWithdraw[asset] = minRsEthAmountToWithdraw_; emit MinAmountToWithdrawUpdated(asset, minRsEthAmountToWithdraw_); } /// @notice update withdrawal delay /// @dev only callable by LRT manager /// @param withdrawalDelayBlocks_ The amount of blocks to wait till to complete a withdraw function setWithdrawalDelayBlocks(uint256 withdrawalDelayBlocks_) external onlyLRTManager { // Set an upper limit of no more than 10 days if (withdrawalDelayBlocks_ > 10 days / 12 seconds) revert ExceedWithdrawalDelay(); withdrawalDelayBlocks = withdrawalDelayBlocks_; emit WithdrawalDelayBlocksUpdated(withdrawalDelayBlocks); } /// @dev Triggers stopped state. Contract must not be paused. function pause() external onlyRole(LRTConstants.PAUSER_ROLE) { _pause(); } /// @dev Returns to normal state. Contract must be paused function unpause() external onlyLRTAdmin { _unpause(); } /*////////////////////////////////////////////////////////////// view functions //////////////////////////////////////////////////////////////*/ /// @notice Get request id /// @param asset Asset address /// @param requestIndex The requests index to generate id for function getRequestId(address asset, uint256 requestIndex) public pure returns (bytes32) { return keccak256(abi.encodePacked(asset, requestIndex)); } /// @notice Get asset amount to receive when trading in rsETH /// @param asset Asset address of LST to receive /// @param amount rsETH amount to convert /// @return underlyingToReceive Amount of underlying to receive function getExpectedAssetAmount( address asset, uint256 amount ) public view override returns (uint256 underlyingToReceive) { // setup oracle contract ILRTOracle lrtOracle = ILRTOracle(lrtConfig.getContract(LRTConstants.LRT_ORACLE)); // calculate underlying asset amount to receive based on rsETH amount and asset exchange rate underlyingToReceive = amount * lrtOracle.rsETHPrice() / lrtOracle.getAssetPrice(asset); } /// @notice Calculates the amount of asset available for withdrawal. /// @param asset The asset address. /// @return availableAssetAmount The asset amount avaialble for withdrawal. function getAvailableAssetAmount(address asset) public view override returns (uint256 availableAssetAmount) { ILRTDepositPool lrtDepositPool = ILRTDepositPool(lrtConfig.getContract(LRTConstants.LRT_DEPOSIT_POOL)); uint256 totalAssets = lrtDepositPool.getTotalAssetDeposits(asset); availableAssetAmount = totalAssets > assetsCommitted[asset] ? totalAssets - assetsCommitted[asset] : 0; } /// @notice View user withdrawal request /// @param asset Asset address /// @param user User address /// @param userIndex Index in list of users withdrawal request function getUserWithdrawalRequest( address asset, address user, uint256 userIndex ) public view override returns (uint256 rsETHAmount, uint256 expectedAssetAmount, uint256 withdrawalStartBlock, uint256 userNonce) { userNonce = userAssociatedNonces[asset][user].at(userIndex); bytes32 requestId = getRequestId(asset, userNonce); rsETHAmount = withdrawalRequests[requestId].rsETHUnstaked; expectedAssetAmount = withdrawalRequests[requestId].expectedAssetAmount; withdrawalStartBlock = withdrawalRequests[requestId].withdrawalStartBlock; } /*////////////////////////////////////////////////////////////// internal functions //////////////////////////////////////////////////////////////*/ /// @notice Registers a new request for withdrawing an asset in exchange for rsETH. /// @param asset The address of the asset being withdrawn. /// @param rsETHUnstaked The amount of rsETH being exchanged. /// @param expectedAssetAmount The expected amount of the asset to be received upon withdrawal completion. function _addUserWithdrawalRequest(address asset, uint256 rsETHUnstaked, uint256 expectedAssetAmount) internal { uint256 nextUnusedNonce_ = nextUnusedNonce[asset]; // Generate a unique identifier for the new withdrawal request. bytes32 requestId = getRequestId(asset, nextUnusedNonce_); // Create and store the new withdrawal request. withdrawalRequests[requestId] = WithdrawalRequest({ rsETHUnstaked: rsETHUnstaked, expectedAssetAmount: expectedAssetAmount, withdrawalStartBlock: block.number }); // Map the user to the newly created request index and increment the nonce for future requests. userAssociatedNonces[asset][msg.sender].pushBack(nextUnusedNonce_); nextUnusedNonce[asset] = nextUnusedNonce_ + 1; emit AssetWithdrawalQueued(msg.sender, asset, rsETHUnstaked, nextUnusedNonce_); } /// @dev Unlocks user withdrawal requests based on current asset availability and prices. /// Iterates through pending requests and unlocks them until the provided asset amount is fully allocated. /// @param asset The asset's address for which withdrawals are being processed. /// @param rsETHPrice Current rsETH to ETH exchange rate. /// @param assetPrice Current asset to ETH exchange rate. /// @param firstExcludedIndex First withdrawal requests index that will not be considered for unlocking. /// @return rsETHAmountToBurn The total amount of rsETH unlocked for withdrawals. /// @return assetAmountToUnlock The total asset amount allocated to unlocked withdrawals. function _unlockWithdrawalRequests( address asset, uint256 availableAssetAmount, uint256 rsETHPrice, uint256 assetPrice, uint256 firstExcludedIndex ) internal returns (uint256 rsETHAmountToBurn, uint256 assetAmountToUnlock) { // Check that upper limit is in the range of existing withdrawal requests. If it is greater set it to the first // nonce with no withdrawal request. if (firstExcludedIndex > nextUnusedNonce[asset]) { firstExcludedIndex = nextUnusedNonce[asset]; } uint256 nextLockedNonce_ = nextLockedNonce[asset]; // Revert when trying to unlock a request that has already been unlocked if (nextLockedNonce_ >= firstExcludedIndex) revert NoPendingWithdrawals(); while (nextLockedNonce_ < firstExcludedIndex) { bytes32 requestId = getRequestId(asset, nextLockedNonce_); WithdrawalRequest storage request = withdrawalRequests[requestId]; // Check that the withdrawal delay has passed since the request's initiation. if (block.number < request.withdrawalStartBlock + withdrawalDelayBlocks) break; // Calculate the amount user will recieve uint256 payoutAmount = _calculatePayoutAmount(request, rsETHPrice, assetPrice); if (availableAssetAmount < payoutAmount) break; // Exit if not enough assets to cover this request assetsCommitted[asset] -= request.expectedAssetAmount; // Set the amount the user will recieve request.expectedAssetAmount = payoutAmount; rsETHAmountToBurn += request.rsETHUnstaked; availableAssetAmount -= payoutAmount; assetAmountToUnlock += payoutAmount; unchecked { nextLockedNonce_++; } } nextLockedNonce[asset] = nextLockedNonce_; } /// @notice Determines the final amount to be disbursed to the user, based on the lesser of the initially /// expected asset amount and the currently calculated return. /// @param request The specific withdrawal request being processed. /// @param rsETHPrice The latest exchange rate of rsETH to ETH. /// @param assetPrice The latest exchange rate of the asset to ETH. /// @return The final amount the user is going to receive. function _calculatePayoutAmount( WithdrawalRequest storage request, uint256 rsETHPrice, uint256 assetPrice ) private view returns (uint256) { uint256 currentReturn = (request.rsETHUnstaked * rsETHPrice) / assetPrice; return (request.expectedAssetAmount < currentReturn) ? request.expectedAssetAmount : currentReturn; } function _createUnlockParams( ILRTOracle lrtOracle, ILRTUnstakingVault unstakingVault, address asset, uint256 firstExcludedIndex ) internal view returns (UnlockParams memory) { return UnlockParams({ rsETHPrice: lrtOracle.rsETHPrice(), assetPrice: lrtOracle.getAssetPrice(asset), totalAvailableAssets: unstakingVault.balanceOf(asset), firstExcludedIndex: firstExcludedIndex }); } function _validatePrices( uint256 rsETHPrice, uint256 assetPrice, uint256 minimumRsEthPrice, uint256 maximumRsEthPrice, uint256 minimumAssetPrice, uint256 maximumAssetPrice ) internal pure { if (rsETHPrice < minimumRsEthPrice || rsETHPrice > maximumRsEthPrice) { revert RsETHPriceOutOfPriceRange(rsETHPrice); } if (assetPrice < minimumAssetPrice || assetPrice > maximumAssetPrice) { revert AssetPriceOutOfPriceRange(assetPrice); } } }
परिवर्तित टेक्स्ट
फ़ाइल खोलें
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.27; import { UtilLib } from "./utils/UtilLib.sol"; import { LRTConstants } from "./utils/LRTConstants.sol"; import { DoubleEndedQueue } from "./utils/DoubleEndedQueue.sol"; import { LRTConfigRoleChecker, ILRTConfig } from "./utils/LRTConfigRoleChecker.sol"; import { IRSETH } from "./interfaces/IRSETH.sol"; import { ILRTOracle } from "./interfaces/ILRTOracle.sol"; import { ILRTWithdrawalManager } from "./interfaces/ILRTWithdrawalManager.sol"; import { ILRTDepositPool } from "./interfaces/ILRTDepositPool.sol"; import { ILRTUnstakingVault } from "./interfaces/ILRTUnstakingVault.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title LRTWithdrawalManager - Withdraw Manager Contract for rsETH => LSTs /// @notice Handles LST asset withdraws contract LRTWithdrawalManager is ILRTWithdrawalManager, LRTConfigRoleChecker, PausableUpgradeable, ReentrancyGuardUpgradeable { using DoubleEndedQueue for DoubleEndedQueue.Uint256Deque; using SafeERC20 for IERC20; mapping(address asset => uint256) public minRsEthAmountToWithdraw; uint256 public withdrawalDelayBlocks; // Next available nonce for withdrawal requests per asset, indicating total requests made. mapping(address asset => uint256 nonce) public nextUnusedNonce; // Next nonce for which a withdrawal request remains locked. mapping(address asset => uint256 requestNonce) public nextLockedNonce; // Mapping from a unique request identifier to its corresponding withdrawal request mapping(bytes32 requestId => WithdrawalRequest) public withdrawalRequests; // Maps each asset to user addresses, pointing to an ordered list of their withdrawal request nonces. // Utilizes a double-ended queue for efficient management and removal of initial requests. mapping(address asset => mapping(address user => DoubleEndedQueue.Uint256Deque requestNonces)) public userAssociatedNonces; // Asset amount committed to be withdrawn by users. mapping(address asset => uint256 amount) public assetsCommitted; mapping(address asset => bool) public isInstantWithdrawalEnabled; uint256 public instantWithdrawalFee; // Fee in basis points (1 = 0.01%) mapping(address asset => uint256) public unlockedWithdrawalsCount; modifier onlySupportedStrategy(address asset) { if (asset != LRTConstants.ETH_TOKEN && lrtConfig.assetStrategy(asset) == address(0)) { revert StrategyNotSupported(); } _; } modifier onlyInstantWithdrawalAllowed(address asset) { if (!isInstantWithdrawalEnabled[asset]) revert InstantWithdrawalNotEnabled(); _; } modifier onlyAfterInitialize2() { if (!isInitialized2()) revert NotInitialized(); _; } /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /// @notice Initializes the contract /// @param lrtConfigAddr LRT config address function initialize(address lrtConfigAddr) external initializer { UtilLib.checkNonZeroAddress(lrtConfigAddr); __Pausable_init(); __ReentrancyGuard_init(); withdrawalDelayBlocks = 8 days / 12 seconds; lrtConfig = ILRTConfig(lrtConfigAddr); emit UpdatedLRTConfig(lrtConfigAddr); } /// @notice Initializes unlocked withdrawals count for ETHx, STETH and ETH /// @param unlockedWithdrawalsCountETHx Initial unlocked withdrawals count for ETHx /// @param unlockedWithdrawalsCountSTETH Initial unlocked withdrawals count for STETH /// @param unlockedWithdrawalsCountETH Initial unlocked withdrawals count for ETH function initialize2( uint256 unlockedWithdrawalsCountETHx, uint256 unlockedWithdrawalsCountSTETH, uint256 unlockedWithdrawalsCountETH ) external reinitializer(2) onlyRole(LRTConstants.UNLOCKED_WITHDRAWAL_INITIALIZER) { unlockedWithdrawalsCount[lrtConfig.getLSTToken(LRTConstants.ST_ETH_TOKEN)] = unlockedWithdrawalsCountSTETH; unlockedWithdrawalsCount[lrtConfig.getLSTToken(LRTConstants.ETHX_TOKEN)] = unlockedWithdrawalsCountETHx; unlockedWithdrawalsCount[LRTConstants.ETH_TOKEN] = unlockedWithdrawalsCountETH; } /*////////////////////////////////////////////////////////////// receive functions //////////////////////////////////////////////////////////////*/ receive() external payable { } /// @dev receive from LRTUnstakingVault function receiveFromLRTUnstakingVault() external payable { } /*////////////////////////////////////////////////////////////// User Withdrawal functions //////////////////////////////////////////////////////////////*/ /// @notice Initiates a withdrawal request for converting rsETH to a specified LST. /// @param asset The LST address the user wants to receive. /// @param rsETHUnstaked The amount of rsETH the user wishes to unstake. /// @dev This function is only callable by the user and is used to initiate a withdrawal request for a specific /// asset. Will be finalised by calling `completeWithdrawal` after the manager unlocked the request and the delay /// has past. There is an edge case were the user withdraws last underlying asset and that asset gets slashed. function initiateWithdrawal( address asset, uint256 rsETHUnstaked, string calldata referralId ) external override nonReentrant whenNotPaused onlySupportedAsset(asset) onlySupportedStrategy(asset) { if (rsETHUnstaked == 0 || rsETHUnstaked < minRsEthAmountToWithdraw[asset]) { revert InvalidAmountToWithdraw(); } IERC20(lrtConfig.rsETH()).safeTransferFrom(msg.sender, address(this), rsETHUnstaked); uint256 expectedAssetAmount = getExpectedAssetAmount(asset, rsETHUnstaked); if (expectedAssetAmount > getAvailableAssetAmount(asset)) revert ExceedAmountToWithdraw(); // preventing over-withdrawal. assetsCommitted[asset] += expectedAssetAmount; _addUserWithdrawalRequest(asset, rsETHUnstaked, expectedAssetAmount); emit ReferralIdEmitted(referralId); } /// @notice Completes a user's withdrawal process by transferring the ETH/LST amount corresponding to the rsETH /// unstaked. /// @param asset The asset address the user wishes to withdraw. function completeWithdrawal( address asset, string calldata referralId ) external nonReentrant whenNotPaused onlyAfterInitialize2 { _processWithdrawalCompletion(asset, msg.sender, referralId); } /// @notice Allows operators to complete a user's withdrawal process /// @param asset The asset address the user wishes to withdraw /// @param user The address of the user whose withdrawal to complete /// @param referralId The referral identifier for tracking /// @dev Not expected to be used for ETH; potential gas grief scenarios are non-impactful for ETH function completeWithdrawalForUser( address asset, address user, string calldata referralId ) external nonReentrant whenNotPaused onlyAfterInitialize2 onlyLRTOperator { _processWithdrawalCompletion(asset, user, referralId); emit AssetWithdrawalCompletedBy(msg.sender); } /// @notice Allows users to instantly withdraw their assets by burning rsETH tokens /// @param asset The address of the asset (ETH/LST) to withdraw /// @param rsETHUnstaked The amount of rsETH tokens to burn for withdrawal /// @param referralId The referral identifier for tracking /// @dev Uses the fee set at execution time. Managers can raise it right before this call, making withdrawals cost /// more than expected. function instantWithdrawal( address asset, uint256 rsETHUnstaked, string calldata referralId ) external nonReentrant whenNotPaused onlySupportedAsset(asset) onlySupportedStrategy(asset) onlyInstantWithdrawalAllowed(asset) { if (rsETHUnstaked == 0 || rsETHUnstaked < minRsEthAmountToWithdraw[asset]) { revert InvalidAmountToWithdraw(); } if (IERC20(lrtConfig.rsETH()).balanceOf(msg.sender) < rsETHUnstaked) revert NotEnoughRsETH(); uint256 assetAmountUnlocked = getExpectedAssetAmount(asset, rsETHUnstaked); IRSETH(lrtConfig.rsETH()).burnFrom(address(msg.sender), rsETHUnstaked); if (assetAmountUnlocked > getAvailableAssetAmount(asset)) revert ExceedAmountToWithdraw(); ILRTUnstakingVault unstakingVault = ILRTUnstakingVault(lrtConfig.getContract(LRTConstants.LRT_UNSTAKING_VAULT)); if (assetAmountUnlocked > unstakingVault.getAssetsAvailableForInstantWithdrawal(asset)) { revert CantInstantWithdrawMoreThanAvailable(); } unstakingVault.redeem(asset, assetAmountUnlocked); uint256 fee = (assetAmountUnlocked * instantWithdrawalFee) / 10_000; uint256 userAmount = assetAmountUnlocked - fee; address treasury = lrtConfig.getContract(LRTConstants.PROTOCOL_TREASURY); _transferAsset(asset, treasury, fee); _transferAsset(asset, msg.sender, userAmount); emit ReferralIdEmitted(referralId); emit AssetWithdrawalFinalized(msg.sender, asset, rsETHUnstaked, userAmount); emit InstantWithdrawalFeeCollected(msg.sender, asset, fee); } /*////////////////////////////////////////////////////////////// operational functions //////////////////////////////////////////////////////////////*/ /// @notice Unlocks assets in the queue up to a specified limit. /// @param asset The address of the asset to unlock. /// @param firstExcludedIndex First withdrawal requests index that will not be considered for unlocking. /// @param minimumAssetPrice The minimum acceptable price for the asset. /// @param minimumRsEthPrice The minimum acceptable price for rsETH. /// @param maximumAssetPrice The maximum acceptable price for the asset. /// @param maximumRsEthPrice The maximum acceptable price for rsETH. function unlockQueue( address asset, uint256 firstExcludedIndex, uint256 minimumAssetPrice, uint256 minimumRsEthPrice, uint256 maximumAssetPrice, uint256 maximumRsEthPrice ) external nonReentrant onlySupportedAsset(asset) whenNotPaused onlyLRTOperator onlyAfterInitialize2 returns (uint256 rsETHBurned, uint256 assetAmountUnlocked) { ILRTOracle lrtOracle = ILRTOracle(lrtConfig.getContract(LRTConstants.LRT_ORACLE)); ILRTUnstakingVault unstakingVault = ILRTUnstakingVault(lrtConfig.getContract(LRTConstants.LRT_UNSTAKING_VAULT)); UnlockParams memory params = _createUnlockParams(lrtOracle, unstakingVault, asset); _validatePrices( params.rsETHPrice, params.assetPrice, minimumRsEthPrice, maximumRsEthPrice, minimumAssetPrice, maximumAssetPrice ); if (params.totalAvailableAssets == 0) revert AmountMustBeGreaterThanZero(); // Updates and unlocks withdrawal requests up to a specified upper limit or until allocated assets are fully // utilized. (rsETHBurned, assetAmountUnlocked) = _unlockWithdrawalRequests( asset, params.totalAvailableAssets, params.rsETHPrice, params.assetPrice, firstExcludedIndex ); if (rsETHBurned != 0) IRSETH(lrtConfig.rsETH()).burnFrom(address(this), rsETHBurned); //Take the amount to distribute from vault unstakingVault.redeem(asset, assetAmountUnlocked); emit AssetUnlocked(asset, rsETHBurned, assetAmountUnlocked, params.rsETHPrice, params.assetPrice); } /*////////////////////////////////////////////////////////////// Manager And Admin Functions //////////////////////////////////////////////////////////////*/ /// @notice update min amount to withdraw /// @dev only callable by LRT admin /// @param asset Asset address /// @param minRsEthAmountToWithdraw_ Minimum amount to withdraw function setMinRsEthAmountToWithdraw(address asset, uint256 minRsEthAmountToWithdraw_) external onlyLRTAdmin { minRsEthAmountToWithdraw[asset] = minRsEthAmountToWithdraw_; emit MinAmountToWithdrawUpdated(asset, minRsEthAmountToWithdraw_); } /// @notice update withdrawal delay /// @dev only callable by LRT manager /// @param withdrawalDelayBlocks_ The amount of blocks to wait till to complete a withdraw function setWithdrawalDelayBlocks(uint256 withdrawalDelayBlocks_) external onlyLRTManager { // Set an upper limit of no more than 16 days if (withdrawalDelayBlocks_ > 16 days / 12 seconds) revert ExceedWithdrawalDelay(); withdrawalDelayBlocks = withdrawalDelayBlocks_; emit WithdrawalDelayBlocksUpdated(withdrawalDelayBlocks); } /// @dev Triggers stopped state. Contract must not be paused. function pause() external onlyRole(LRTConstants.PAUSER_ROLE) { _pause(); } /// @dev Returns to normal state. Contract must be paused function unpause() external onlyLRTAdmin { _unpause(); } /// @notice Enables or disables instant withdrawal for a specific asset /// @param asset The asset address to configure /// @param enabled Whether instant withdrawal should be enabled /// @dev Only callable by LRTManager function setInstantWithdrawalEnabled(address asset, bool enabled) external onlySupportedAsset(asset) onlyLRTManager { isInstantWithdrawalEnabled[asset] = enabled; emit InstantWithdrawalEnabledUpdated(asset, enabled); } /// @notice Sets the instant withdrawal fee /// @param feeBasisPoints The fee in basis points (1 = 0.01%) /// @dev Only callable by LRTManager function setInstantWithdrawalFee(uint256 feeBasisPoints) external onlyLRTManager { if (feeBasisPoints > 1000) revert FeeTooHigh(); // Max 10% instantWithdrawalFee = feeBasisPoints; emit InstantWithdrawalFeeUpdated(feeBasisPoints); } /// @notice Sweep remaining assets to treasury when all withdrawals are completed /// @param asset The asset address to sweep /// @return transferredAmount The amount swept to treasury /// @dev This function can only be called when no unlocked withdrawals exist for the asset /// @dev Not expected to be used for ETH; ETH should not accumulate requiring sweeping function sweepRemainingAssets(address asset) external nonReentrant onlySupportedAsset(asset) onlyLRTManager onlyAfterInitialize2 returns (uint256 transferredAmount) { // Check that all withdrawals are completed if (hasUnlockedWithdrawals(asset)) revert PendingWithdrawalsExist(); uint256 balance = _getAssetBalance(asset); if (balance == 0) revert AmountMustBeGreaterThanZero(); // Transfer to treasury address treasury = lrtConfig.getContract(LRTConstants.PROTOCOL_TREASURY); _transferAsset(asset, treasury, balance); emit RemainingAssetsSwept(asset, balance, treasury); return balance; } /*////////////////////////////////////////////////////////////// view functions //////////////////////////////////////////////////////////////*/ /// @notice Get request id /// @param asset Asset address /// @param requestIndex The requests index to generate id for function getRequestId(address asset, uint256 requestIndex) public pure returns (bytes32) { return keccak256(abi.encodePacked(asset, requestIndex)); } /// @notice Get asset amount to receive when trading in rsETH /// @param asset Asset address of LST to receive /// @param amount rsETH amount to convert /// @return underlyingToReceive Amount of underlying to receive function getExpectedAssetAmount( address asset, uint256 amount ) public view override returns (uint256 underlyingToReceive) { // setup oracle contract ILRTOracle lrtOracle = ILRTOracle(lrtConfig.getContract(LRTConstants.LRT_ORACLE)); // calculate underlying asset amount to receive based on rsETH amount and asset exchange rate underlyingToReceive = amount * lrtOracle.rsETHPrice() / lrtOracle.getAssetPrice(asset); } /// @notice Calculates the amount of asset available for withdrawal. /// @param asset The asset address. /// @return availableAssetAmount The asset amount avaialble for withdrawal. function getAvailableAssetAmount(address asset) public view override returns (uint256 availableAssetAmount) { ILRTDepositPool lrtDepositPool = ILRTDepositPool(lrtConfig.getContract(LRTConstants.LRT_DEPOSIT_POOL)); uint256 totalAssets = lrtDepositPool.getTotalAssetDeposits(asset); availableAssetAmount = totalAssets > assetsCommitted[asset] ? totalAssets - assetsCommitted[asset] : 0; } /// @notice View user withdrawal request /// @param asset Asset address /// @param user User address /// @param userIndex Index in list of users withdrawal request function getUserWithdrawalRequest( address asset, address user, uint256 userIndex ) public view override returns (uint256 rsETHAmount, uint256 expectedAssetAmount, uint256 withdrawalStartBlock, uint256 userNonce) { userNonce = userAssociatedNonces[asset][user].at(userIndex); bytes32 requestId = getRequestId(asset, userNonce); rsETHAmount = withdrawalRequests[requestId].rsETHUnstaked; expectedAssetAmount = withdrawalRequests[requestId].expectedAssetAmount; withdrawalStartBlock = withdrawalRequests[requestId].withdrawalStartBlock; } /// @notice Check if the contract is initialized with version 2 /// @return isInitialized True if the contract is initialized with version 2, false otherwise function isInitialized2() public view returns (bool) { return _getInitializedVersion() >= 2; } /// @notice Check if there are any unlocked withdrawals for a specific asset /// @param asset The asset address /// @return hasUnlocked True if there are unlocked withdrawals, false otherwise function hasUnlockedWithdrawals(address asset) public view returns (bool hasUnlocked) { return unlockedWithdrawalsCount[asset] > 0; } /*////////////////////////////////////////////////////////////// internal functions //////////////////////////////////////////////////////////////*/ /// @notice Processes the completion of a withdrawal request for a user. /// @param asset The asset address of the withdrawal. /// @param user The address of the user whose withdrawal is being completed. /// @param referralId The referral identifier for tracking. function _processWithdrawalCompletion(address asset, address user, string calldata referralId) internal { if (userAssociatedNonces[asset][user].empty()) { revert NoWithdrawalRequests(user, asset); } // Retrieve and remove the oldest withdrawal request for the user. uint256 usersFirstWithdrawalRequestNonce = userAssociatedNonces[asset][user].popFront(); // Ensure the request is already unlocked. if (usersFirstWithdrawalRequestNonce >= nextLockedNonce[asset]) revert WithdrawalLocked(); bytes32 requestId = getRequestId(asset, usersFirstWithdrawalRequestNonce); WithdrawalRequest memory request = withdrawalRequests[requestId]; delete withdrawalRequests[requestId]; // Check that the withdrawal delay has passed since the request's initiation. if (block.number < request.withdrawalStartBlock + withdrawalDelayBlocks) revert WithdrawalDelayNotPassed(); unlockedWithdrawalsCount[asset]--; _transferAsset(asset, user, request.expectedAssetAmount); emit ReferralIdEmitted(referralId); emit AssetWithdrawalFinalized(user, asset, request.rsETHUnstaked, request.expectedAssetAmount); } /// @notice Registers a new request for withdrawing an asset in exchange for rsETH. /// @param asset The address of the asset being withdrawn. /// @param rsETHUnstaked The amount of rsETH being exchanged. /// @param expectedAssetAmount The expected amount of the asset to be received upon withdrawal completion. function _addUserWithdrawalRequest(address asset, uint256 rsETHUnstaked, uint256 expectedAssetAmount) internal { uint256 nextUnusedNonce_ = nextUnusedNonce[asset]; // Generate a unique identifier for the new withdrawal request. bytes32 requestId = getRequestId(asset, nextUnusedNonce_); // Create and store the new withdrawal request. withdrawalRequests[requestId] = WithdrawalRequest({ rsETHUnstaked: rsETHUnstaked, expectedAssetAmount: expectedAssetAmount, withdrawalStartBlock: block.number }); // Map the user to the newly created request index and increment the nonce for future requests. userAssociatedNonces[asset][msg.sender].pushBack(nextUnusedNonce_); nextUnusedNonce[asset] = nextUnusedNonce_ + 1; emit AssetWithdrawalQueued(msg.sender, asset, rsETHUnstaked, nextUnusedNonce_); } /// @dev Unlocks user withdrawal requests based on current asset availability and prices. /// Iterates through pending requests and unlocks them until the provided asset amount is fully allocated. /// @param asset The asset's address for which withdrawals are being processed. /// @param rsETHPrice Current rsETH to ETH exchange rate. /// @param assetPrice Current asset to ETH exchange rate. /// @param firstExcludedIndex First withdrawal requests index that will not be considered for unlocking. /// @return rsETHAmountToBurn The total amount of rsETH unlocked for withdrawals. /// @return assetAmountToUnlock The total asset amount allocated to unlocked withdrawals. function _unlockWithdrawalRequests( address asset, uint256 availableAssetAmount, uint256 rsETHPrice, uint256 assetPrice, uint256 firstExcludedIndex ) internal returns (uint256 rsETHAmountToBurn, uint256 assetAmountToUnlock) { // Check that upper limit is in the range of existing withdrawal requests. If it is greater set it to the first // nonce with no withdrawal request. if (firstExcludedIndex > nextUnusedNonce[asset]) { firstExcludedIndex = nextUnusedNonce[asset]; } uint256 nextLockedNonce_ = nextLockedNonce[asset]; // Revert when trying to unlock a request that has already been unlocked if (nextLockedNonce_ >= firstExcludedIndex) revert NoPendingWithdrawals(); while (nextLockedNonce_ < firstExcludedIndex) { bytes32 requestId = getRequestId(asset, nextLockedNonce_); WithdrawalRequest storage request = withdrawalRequests[requestId]; // Check that the withdrawal delay has passed since the request's initiation. if (block.number < request.withdrawalStartBlock + withdrawalDelayBlocks) break; // Calculate the amount user will receive uint256 payoutAmount = _calculatePayoutAmount(request, rsETHPrice, assetPrice); if (availableAssetAmount < payoutAmount) break; // Exit if not enough assets to cover this request assetsCommitted[asset] -= request.expectedAssetAmount; // Set the amount the user will receive request.expectedAssetAmount = payoutAmount; rsETHAmountToBurn += request.rsETHUnstaked; availableAssetAmount -= payoutAmount; assetAmountToUnlock += payoutAmount; unlockedWithdrawalsCount[asset]++; unchecked { nextLockedNonce_++; } } nextLockedNonce[asset] = nextLockedNonce_; } /// @notice Determines the final amount to be disbursed to the user, based on the lesser of the initially /// expected asset amount and the currently calculated return. /// @param request The specific withdrawal request being processed. /// @param rsETHPrice The latest exchange rate of rsETH to ETH. /// @param assetPrice The latest exchange rate of the asset to ETH. /// @return The final amount the user is going to receive. function _calculatePayoutAmount( WithdrawalRequest storage request, uint256 rsETHPrice, uint256 assetPrice ) private view returns (uint256) { uint256 currentReturn = (request.rsETHUnstaked * rsETHPrice) / assetPrice; return (request.expectedAssetAmount < currentReturn) ? request.expectedAssetAmount : currentReturn; } function _createUnlockParams( ILRTOracle lrtOracle, ILRTUnstakingVault unstakingVault, address asset ) internal view returns (UnlockParams memory) { return UnlockParams({ rsETHPrice: lrtOracle.rsETHPrice(), assetPrice: lrtOracle.getAssetPrice(asset), totalAvailableAssets: unstakingVault.balanceOf(asset) }); } function _validatePrices( uint256 rsETHPrice, uint256 assetPrice, uint256 minimumRsEthPrice, uint256 maximumRsEthPrice, uint256 minimumAssetPrice, uint256 maximumAssetPrice ) internal pure { if (rsETHPrice < minimumRsEthPrice || rsETHPrice > maximumRsEthPrice) { revert RsETHPriceOutOfPriceRange(rsETHPrice); } if (assetPrice < minimumAssetPrice || assetPrice > maximumAssetPrice) { revert AssetPriceOutOfPriceRange(assetPrice); } } /// @dev Transfer assets (ETH or ERC20) to a recipient /// @param asset The asset address /// @param to The recipient address /// @param amount The amount to transfer function _transferAsset(address asset, address to, uint256 amount) internal { if (asset == LRTConstants.ETH_TOKEN) { (bool sent,) = payable(to).call{ value: amount }(""); if (!sent) revert EthTransferFailed(); } else { IERC20(asset).safeTransfer(to, amount); } } /// @dev Get the balance of an asset (ETH or ERC20) held by this contract /// @param asset The asset address /// @return balance The asset balance of this contract function _getAssetBalance(address asset) internal view returns (uint256 balance) { return asset == LRTConstants.ETH_TOKEN ? address(this).balance : IERC20(asset).balanceOf(address(this)); } }
अंतर खोजें