Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions mainnet-contracts/src/PufferWithdrawalManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,48 @@ contract PufferWithdrawalManager is
});
}

/**
* @inheritdoc IPufferWithdrawalManager
* @dev Allows users to cancel their withdrawal requests and receive back pufETH
*/
function cancelWithdrawal(uint256 withdrawalIdx) external {
WithdrawalManagerStorage storage $ = _getWithdrawalManagerStorage();

require(withdrawalIdx < $.withdrawals.length, WithdrawalDoesNotExist());

Withdrawal memory withdrawal = $.withdrawals[withdrawalIdx];
address recipient = withdrawal.recipient;

// Check if withdrawal has already been completed (recipient is set to address(0) when completed)
require(recipient != address(0), WithdrawalAlreadyCompleted());

// Check if the caller is the original recipient
require(recipient == msg.sender, NotWithdrawalOwner());

// Check if the withdrawal's batch has been finalized
uint256 batchIndex = withdrawalIdx / BATCH_SIZE;
require(batchIndex > $.finalizedWithdrawalBatch, WithdrawalAlreadyFinalized());

WithdrawalBatch storage batch = $.withdrawalBatches[batchIndex];

// Treat this canceled withdrawal as a claimed withdrawal to avoid issues in the `returnExcessETHToVault` function
++batch.withdrawalsClaimed;

uint256 pufETHAmount = withdrawal.pufETHAmount;

uint256 expectedETHAmount = (pufETHAmount * withdrawal.pufETHToETHExchangeRate) / 1 ether;
batch.toBurn -= uint88(pufETHAmount);
batch.toTransfer -= uint96(expectedETHAmount);

// Clear the withdrawal data
delete $.withdrawals[withdrawalIdx];

// Transfer pufETH back to the user
PUFFER_VAULT.transfer(recipient, pufETHAmount);

emit WithdrawalCancelled({ withdrawalIdx: withdrawalIdx, pufETHAmount: pufETHAmount, recipient: recipient });
}

/**
* @inheritdoc IPufferWithdrawalManager
* @dev Restricted access to ROLE_ID_OPERATIONS_MULTISIG
Expand All @@ -230,6 +272,7 @@ contract PufferWithdrawalManager is
uint256 totalExcessETH = 0;

for (uint256 i = 0; i < batchIndices.length; ++i) {
require(batchIndices[i] <= $.finalizedWithdrawalBatch, NotFinalized());
WithdrawalBatch storage batch = $.withdrawalBatches[batchIndices[i]];

require(batch.withdrawalsClaimed == BATCH_SIZE, NotAllWithdrawalsClaimed());
Expand Down
29 changes: 29 additions & 0 deletions mainnet-contracts/src/interface/IPufferWithdrawalManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ interface IPufferWithdrawalManager {
*/
error WithdrawalAmountTooHigh();

/**
* @notice Thrown when attempting to cancel a withdrawal that has already been finalized
*/
error WithdrawalAlreadyFinalized();

/**
* @notice Thrown when attempting to cancel a withdrawal that doesn't exist
*/
error WithdrawalDoesNotExist();

/**
* @notice Thrown when attempting to cancel a withdrawal that you don't own
*/
error NotWithdrawalOwner();

/**
* @notice Emitted when a withdrawal is requested
* @param withdrawalIdx The index of the requested withdrawal
Expand Down Expand Up @@ -119,6 +134,14 @@ interface IPufferWithdrawalManager {
*/
event ExcessETHReturned(uint256[] batchIndices, uint256 totalExcessETH);

/**
* @notice Emitted when a withdrawal is cancelled
* @param withdrawalIdx The index of the cancelled withdrawal
* @param pufETHAmount The amount of pufETH returned to the user
* @param recipient The address that received the returned pufETH
*/
event WithdrawalCancelled(uint256 indexed withdrawalIdx, uint256 pufETHAmount, address indexed recipient);

/**
* @notice Returns the address of the PufferVaultV5 contract
* @return The address of the PufferVaultV5 contract
Expand Down Expand Up @@ -154,6 +177,12 @@ interface IPufferWithdrawalManager {
*/
function completeQueuedWithdrawal(uint256 withdrawalIdx) external;

/**
* @notice Cancel a withdrawal request and receive back the pufETH
* @param withdrawalIdx The index of the withdrawal to cancel
*/
function cancelWithdrawal(uint256 withdrawalIdx) external;

/**
* @notice Returns the excess ETH transferred from the Vault to the WithdrawalManager
* This can happen if there is a discrepancy between the expected ETH amount and the actual ETH amount withdrawn because of the pufETH:ETH exchange rate.
Expand Down
Loading
Loading