Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods for batch withdrawals requests and claiming #513

Merged
178 changes: 103 additions & 75 deletions contracts/0.8.9/WithdrawalQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/* See contracts/COMPILERS.md */
pragma solidity 0.8.9;

import {WithdrawalQueueBase} from "./WithdrawalQueueBase.sol";
import {WithdrawalQueueBase} from "./WithdrawalQueueBase.sol";

import {IERC20} from "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts-v4.4/token/ERC721/IERC721.sol";
Expand Down Expand Up @@ -116,6 +116,8 @@ contract WithdrawalQueue is AccessControlEnumerable, WithdrawalQueueBase {
error ResumedExpected();
error RequestAmountTooSmall(uint256 _amountOfStETH);
error RequestAmountTooLarge(uint256 _amountOfStETH);
error LengthsMismatch(uint256 _expectedLength, uint256 _actualLength);
error RequestIdsNotSorted();

/// @notice Reverts when the contract is uninitialized
modifier whenInitialized() {
Expand All @@ -140,7 +142,7 @@ contract WithdrawalQueue is AccessControlEnumerable, WithdrawalQueueBase {
}
_;
}

/**
* @param _wstETH address of WstETH contract
*/
Expand All @@ -154,7 +156,7 @@ contract WithdrawalQueue is AccessControlEnumerable, WithdrawalQueueBase {
}

/**
* @notice Intialize the contract storage explicitly.
* @notice Intialize the contract storage explicitly.
* @param _admin admin address that can change every role.
* @param _pauser address that will be able to pause the withdrawals
* @param _resumer address that will be able to resume the withdrawals after pause
Expand Down Expand Up @@ -203,92 +205,118 @@ contract WithdrawalQueue is AccessControlEnumerable, WithdrawalQueueBase {
return !RESUMED_POSITION.getStorageBool();
}

/**
* @notice Request withdrawal of the provided stETH token amount
* @param _amountOfStETH StETH tokens that will be locked for withdrawal
* @param _recipient address to send ether to upon withdrawal. Will be set to `msg.sender` if `address(0)` is passed
* @dev Reverts with `ResumedExpected()` if contract is paused
* @dev Reverts with `RequestAmountTooSmall(_amountOfStETH)` if amount is less than `MIN_STETH_WITHDRAWAL_AMOUNT`
* @dev Reverts with `RequestAmountTooLarge(_amountOfStETH)` if amount is greater than `MAX_STETH_WITHDRAWAL_AMOUNT`
* @dev Reverts if failed to transfer StETH to the contract
*/
function requestWithdrawal(uint256 _amountOfStETH, address _recipient)
external
struct WithdrawalRequestInput {
/// @notice Amount of the wstETH/StETH tokens that will be locked for withdrawal
uint256 amount;
/// @notice Address to send ether to upon withdrawal
address recipient;
}

/// @notice Request the sequence of stETH withdrawals according to passed `withdrawalRequestInputs` data
/// @param _withdrawalRequestInputs an array of `WithdrawalRequestInput` data. The standalone withdrawal request will
/// be created for each item in the passed list. If `WithdrawalRequestInput.recipient` is set to `address(0)`,
/// `msg.sender` will be used as recipient.
/// @return requestIds an array of the created withdrawal requests
function requestWithdrawals(WithdrawalRequestInput[] calldata _withdrawalRequestInputs)
public
whenResumed
returns (uint256 requestId)
returns (uint256[] memory requestIds)
{
_recipient = _checkWithdrawalRequestInput(_amountOfStETH, _recipient);
requestIds = new uint256[](_withdrawalRequestInputs.length);
for (uint256 i = 0; i < _withdrawalRequestInputs.length; ++i) {
requestIds[i] = _requestWithdrawal(
_withdrawalRequestInputs[i].amount,
_checkWithdrawalRequestInput(_withdrawalRequestInputs[i].amount, _withdrawalRequestInputs[i].recipient)
);
}
}

return _requestWithdrawal(_amountOfStETH, _recipient);
/// @notice Request the sequence of wstETH withdrawals according to passed `withdrawalRequestInputs` data
/// @param _withdrawalRequestInputs an array of `WithdrawalRequestInput` data. The standalone withdrawal request will
/// be created for each item in the passed list. If `WithdrawalRequestInput.recipient` is set to `address(0)`,
/// `msg.sender` will be used as recipient.
/// @return requestIds an array of the created withdrawal requests
function requestWithdrawalsWstETH(WithdrawalRequestInput[] calldata _withdrawalRequestInputs)
public
whenResumed
returns (uint256[] memory requestIds)
{
requestIds = new uint256[](_withdrawalRequestInputs.length);
for (uint256 i = 0; i < _withdrawalRequestInputs.length; ++i) {
uint256 amountOfWstETH = _withdrawalRequestInputs[i].amount;
address recipient = _checkWithdrawalRequestInput(
IWstETH(WSTETH).getStETHByWstETH(amountOfWstETH),
_withdrawalRequestInputs[i].recipient
);
requestIds[i] = _requestWithdrawalWstETH(amountOfWstETH, recipient);
}
}

/**
* @notice Request withdrawal of the provided stETH token amount using EIP-2612 Permit
* @param _amountOfStETH StETH tokens that will be locked for withdrawal
* @param _recipient address to send ether to upon withdrawal. Will be set to `msg.sender` if `address(0)` is passed
*/
function requestWithdrawalWithPermit(
uint256 _amountOfStETH,
address _recipient,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external whenResumed returns (uint256 requestId) {
_recipient = _checkWithdrawalRequestInput(_amountOfStETH, _recipient);

STETH.permit(msg.sender, address(this), _amountOfStETH, _deadline, _v, _r, _s);

return _requestWithdrawal(_amountOfStETH, _recipient);
struct Permit {
uint256 value;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}

/**
* @notice Request withdrawal of the provided wstETH token amount
* @param _amountOfWstETH StETH tokens that will be locked for withdrawal
* @param _recipient address to send ether to upon withdrawal. Will be set to `msg.sender` if `address(0)` is passed
*/
function requestWithdrawalWstETH(uint256 _amountOfWstETH, address _recipient)
external
whenResumed
returns (uint256 requestId)
{
_recipient = _checkWithdrawalRequestInput(IWstETH(WSTETH).getStETHByWstETH(_amountOfWstETH), _recipient);
return _requestWithdrawalWstETH(_amountOfWstETH, _recipient);
/// @notice Request the sequence of stETH withdrawals according to passed `withdrawalRequestInputs` data using EIP-2612 Permit
/// @param _withdrawalRequestInputs an array of `WithdrawalRequestInput` data. The standalone withdrawal request will
/// be created for each item in the passed list. If `WithdrawalRequestInput.recipient` is set to `address(0)`,
/// `msg.sender` will be used as recipient.
/// @return requestIds an array of the created withdrawal requests
folkyatina marked this conversation as resolved.
Show resolved Hide resolved
function requestWithdrawalsWithPermit(
WithdrawalRequestInput[] calldata _withdrawalRequestInputs,
Permit calldata _permit
) external whenResumed returns (uint256[] memory requestIds) {
STETH.permit(msg.sender, address(this), _permit.value, _permit.deadline, _permit.v, _permit.r, _permit.s);
return requestWithdrawals(_withdrawalRequestInputs);
}

/**
* @notice Request withdrawal of the provided wstETH token amount using EIP-2612 Permit
* @param _amountOfWstETH StETH tokens that will be locked for withdrawal
* @param _recipient address to send ether to upon withdrawal. Will be set to `msg.sender` if `address(0)` is passed
*/
function requestWithdrawalWstETHWithPermit(
uint256 _amountOfWstETH,
address _recipient,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external whenResumed returns (uint256 requestId) {
_recipient = _checkWithdrawalRequestInput(IWstETH(WSTETH).getStETHByWstETH(_amountOfWstETH), _recipient);
WSTETH.permit(msg.sender, address(this), _amountOfWstETH, _deadline, _v, _r, _s);
return _requestWithdrawalWstETH(_amountOfWstETH, _recipient);
/// @notice Request the sequence of wstETH withdrawals according to passed `withdrawalRequestInputs` data using EIP-2612 Permit
/// @param _withdrawalRequestInputs an array of `WithdrawalRequestInput` data. The standalone withdrawal request will
/// be created for each item in the passed list. If `WithdrawalRequestInput.recipient` is set to `address(0)`,
/// `msg.sender` will be used as recipient.
/// @return requestIds an array of the created withdrawal requests
function requestWithdrawalsWstETHWithPermit(
WithdrawalRequestInput[] calldata _withdrawalRequestInputs,
Permit calldata _permit
) external whenResumed returns (uint256[] memory requestIds) {
WSTETH.permit(msg.sender, address(this), _permit.value, _permit.deadline, _permit.v, _permit.r, _permit.s);
return requestWithdrawalsWstETH(_withdrawalRequestInputs);
}

/// @notice Request withdrawal of the provided token amount in a batch
/// NB: Always reverts
function requestWithdrawalBatch(uint256[] calldata, address[] calldata)
external
view
whenResumed
returns (uint256[] memory)
{
revert Unimplemented();
struct ClaimWithdrawalInput {
/// @notice id of the finalized requests to claim
uint256 requestId;
/// @notice rate index found offchain that should be used for claiming
folkyatina marked this conversation as resolved.
Show resolved Hide resolved
uint256 hint;
}

/// @notice Claim withdrawals batch once finalized (claimable)
/// NB: Always reverts
function claimWithdrawalBatch(uint256[] calldata /*_requests*/ ) external pure {
revert Unimplemented();
/// @param _claimWithdrawalInputs list of withdrawal request ids and hints to claim
function claimWithdrawals(ClaimWithdrawalInput[] calldata _claimWithdrawalInputs) external {
for (uint256 i = 0; i < _claimWithdrawalInputs.length; ++i) {
claimWithdrawal(_claimWithdrawalInputs[i].requestId, _claimWithdrawalInputs[i].hint);
}
}

/// @notice Returns the list of hints for the given request ids
/// @param _requestIds ids of the requests sorted in the descending order to get hints for
/// @return hintIds the hints for `claimWithdrawal` to find the discount for the passed request ids
function findClaimHints(uint256[] calldata _requestIds, uint256 _firstIndex, uint256 _lastIndex)
folkyatina marked this conversation as resolved.
Show resolved Hide resolved
external
view
returns (uint256[] memory hintIds)
{
hintIds = new uint256[](_requestIds.length);
uint256 prevRequestId = type(uint256).max;
for (uint256 i = 0; i < _requestIds.length; ++i) {
if (_requestIds[i] > prevRequestId) revert RequestIdsNotSorted();
hintIds[i] = findClaimHint(_requestIds[i], _firstIndex, _lastIndex);
TheDZhon marked this conversation as resolved.
Show resolved Hide resolved
prevRequestId = _requestIds[i];
_lastIndex = hintIds[i];
}
}

/**
Expand Down
6 changes: 3 additions & 3 deletions contracts/0.8.9/WithdrawalQueueBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ abstract contract WithdrawalQueueBase {
* @param _requestId request id to claim
* @param _hint rate index found offchain that should be used for claiming
*/
function claimWithdrawal(uint256 _requestId, uint256 _hint) external {
function claimWithdrawal(uint256 _requestId, uint256 _hint) public {
if (_requestId == 0) revert ZeroRequestId();
if (_requestId > lastFinalizedRequestId) revert RequestNotFinalized(_requestId);

Expand Down Expand Up @@ -266,8 +266,8 @@ abstract contract WithdrawalQueueBase {

/// @dev creates a new `WithdrawalRequest` in the queue
function _enqueue(uint256 _amountOfStETH, uint256 _amountofShares, address _recipient)
internal
returns (uint256 requestId)
internal
returns (uint256 requestId)
{
WithdrawalRequest memory lastRequest = queue[lastRequestId];

Expand Down
2 changes: 1 addition & 1 deletion hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const getNetConfig = (networkName, ethAccountName) => {
initialBaseFeePerGas: 0,
allowUnlimitedContractSize: true,
accounts: {
mnemonic: 'hardhat',
mnemonic: 'test test test test test test test test test test test junk',
count: 20,
accountsBalance: '100000000000000000000000',
gasPrice: 0
Expand Down
Loading