Skip to content

Commit

Permalink
Merge pull request #43 from chimera-defi/withdrawQ/cancel
Browse files Browse the repository at this point in the history
Withdraw q/cancel
  • Loading branch information
chimera-defi authored Sep 4, 2024
2 parents 355e281 + eea73b2 commit 32314bc
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 5 deletions.
46 changes: 46 additions & 0 deletions contracts/v2/core/WithdrawalQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ contract WithdrawalQueue is AccessControl, ReentrancyGuard, GranularPause, FIFOQ
uint256 assets
);
event Redeem(address indexed requester, address indexed receiver, uint256 shares, uint256 assets);
event CancelRedeem(address indexed requester, address indexed receiver, uint256 shares, uint256 assets);

constructor(address _minter, address _wsgEth, uint256 _epochLength) FIFOQueue(_epochLength) {
MINTER = _minter;
Expand All @@ -76,6 +77,14 @@ contract WithdrawalQueue is AccessControl, ReentrancyGuard, GranularPause, FIFOQ
_grantRole(GOV, msg.sender);
}

/// @notice Requests a redemption of vault assets.
/// @dev This function must be called by either the owner or an operator of the vault, and is only allowed when the contract is not paused.

/// @param shares The number of shares to redeem.
/// @param requester The address requesting the redemption.
/// @param owner The owner of the vault being redeemed from.

/// @return requestId The unique ID assigned to this redemption request.
function requestRedeem(
uint256 shares,
address requester,
Expand All @@ -98,6 +107,14 @@ contract WithdrawalQueue is AccessControl, ReentrancyGuard, GranularPause, FIFOQ
emit RedeemRequest(requester, owner, requestId, msg.sender, shares);
}

/// @notice Allows a user to redeem their vault shares.
/// @dev This function must be called by either the owner or an operator of the requester's vault, and is only allowed when the contract is not paused.

/// @param shares The number of shares to redeem.
/// @param receiver The address that will receive the redeemed assets.
/// @param requester The address requesting the redemption.

/// @return assets The amount of assets that were successfully redeemed.
function redeem(
uint256 shares,
address receiver,
Expand Down Expand Up @@ -137,6 +154,35 @@ contract WithdrawalQueue is AccessControl, ReentrancyGuard, GranularPause, FIFOQ
emit Redeem(requester, receiver, shares, assets);
}

/// @notice Cancel a redeem request and return funds to owner. Can only be done after the epoch has expired
function cancelRedeem(
address receiver,
address requester
) external onlyOwnerOrOperator(requester) nonReentrant whenNotPaused(uint16(3)) returns (uint256 assets) {
uint256 shares = pendingRedeemRequest(requester);
assets = IERC4626(WSGETH).previewRedeem(shares);

if (shares == 0) {
revert Errors.InvalidAmount();
}

_verifyEpochHasElapsed(requester);

// checks if we have enough assets to fulfill the request and if epoch has passed
if (claimableRedeemRequest(requester) < assets) {
_checkWithdraw(requester, totalBalance(), assets);
return 0; // should never happen. previous fn will generate a rich error
}

// Treat everything as claimableRedeemRequest and validate here if there's adequate funds
redeemRequests[requester] -= assets; // underflow would revert if not enough claimable shares
totalPendingRequest -= assets;
_withdraw(requester, assets);
IERC20(WSGETH).transfer(receiver, shares); // asset here is the Vault underlying asset

emit CancelRedeem(requester, receiver, shares, assets);
}

function togglePause(uint16 func) external onlyRole(GOV) {
bool paused = paused[func];
if (paused) {
Expand Down
14 changes: 9 additions & 5 deletions contracts/v2/lib/FIFOQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ abstract contract FIFOQueue {
epochLength = _epochLength;
}

function _verifyEpochHasElapsed(address sender) public view returns (bool epochElapsed) {
UserEntry memory ue = userEntries[sender];
if (!(block.number >= ue.blocknum + epochLength)) {
revert Errors.TooEarly();
}
return true;
}

function _checkWithdraw(
address sender,
uint256 balanceOfSelf,
Expand All @@ -30,11 +38,7 @@ abstract contract FIFOQueue {
if (!(amountToWithdraw <= balanceOfSelf && amountToWithdraw <= ue.amount)) {
revert Errors.InvalidAmount();
}

if (!(block.number >= ue.blocknum + epochLength)) {
revert Errors.TooEarly();
}
return true;
return _verifyEpochHasElapsed(sender);
}

function _isWithdrawalAllowed(
Expand Down
28 changes: 28 additions & 0 deletions test/v2/core/withdrawQueue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ describe("WithdrawalQueue", () => {
});
});

it("test cancelRedeem flow", async () => {
// make redeem request
await expect(withdrawalQueue.connect(alice).requestRedeem(parseEther("10"), alice.address, alice.address))
.to.be.emit(withdrawalQueue, "RedeemRequest")
.withArgs(alice.address, alice.address, 0, alice.address, parseEther("10"));
await expect(withdrawalQueue.connect(bob).requestRedeem(parseEther("30"), bob.address, bob.address))
.to.be.emit(withdrawalQueue, "RedeemRequest")
.withArgs(bob.address, bob.address, 1, bob.address, parseEther("30"));

// confirm redeem request is processeable
expect(await withdrawalQueue.pendingRedeemRequest(alice.address)).to.eq(parseEther("10"));
expect(await withdrawalQueue.pendingRedeemRequest(bob.address)).to.eq(parseEther("30"));
await advanceTimeAndBlock(1);
expect(await withdrawalQueue.claimableRedeemRequest(alice.address)).to.eq(parseEther("10"));
expect(await withdrawalQueue.claimableRedeemRequest(bob.address)).to.eq(parseEther("30"));

// cancel 1
await expect(withdrawalQueue.connect(alice).cancelRedeem(alice.address, alice.address))
.to.be.emit(withdrawalQueue, "CancelRedeem")
.withArgs(alice.address, alice.address, parseEther("10"), parseEther("10"));

await expect(withdrawalQueue.connect(alice).redeem(parseEther("10"), alice.address, alice.address))
.to.be.revertedWithCustomError(withdrawalQueue, "InvalidAmount")
.withArgs();

await withdrawalQueue.connect(bob).redeem(parseEther("30"), bob.address, bob.address);
});

it("request redeem flow", async () => {
await expect(withdrawalQueue.connect(alice).requestRedeem(parseEther("10"), alice.address, alice.address))
.to.be.emit(withdrawalQueue, "RedeemRequest")
Expand Down

0 comments on commit 32314bc

Please sign in to comment.