Skip to content

Commit

Permalink
Simplification of claimRedeem
Browse files Browse the repository at this point in the history
  • Loading branch information
naddison36 committed Oct 3, 2024
1 parent ff40876 commit 87ef2f5
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 80 deletions.
65 changes: 18 additions & 47 deletions src/contracts/AbstractARM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,22 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable {
uint256 public traderate1;

/// @notice cumulative total of all withdrawal requests included the ones that have already been claimed
uint128 public withdrawsQueued;
uint120 public withdrawsQueued;
/// @notice total of all the withdrawal requests that have been claimed
uint128 public withdrawsClaimed;
/// @notice cumulative total of all the withdrawal requests that can be claimed including the ones already claimed
uint128 public withdrawsClaimable;
uint120 public withdrawsClaimed;
/// @notice index of the next withdrawal request starting at 0
uint128 public nextWithdrawalIndex;
uint16 public nextWithdrawalIndex;

struct WithdrawalRequest {
address withdrawer;
bool claimed;
// When the withdrawal can be claimed
uint40 claimTimestamp;
// Amount of assets to withdraw
uint128 assets;
uint120 assets;
// cumulative total of all withdrawal requests including this one.
// this request can be claimed when this queued amount is less than or equal to the queue's claimable amount.
uint128 queued;
uint120 queued;
}

/// @notice Mapping of withdrawal request indices to the user withdrawal request data
Expand Down Expand Up @@ -440,19 +438,19 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable {
assets = convertToAssets(shares);

requestId = nextWithdrawalIndex;
uint128 queued = SafeCast.toUint128(withdrawsQueued + assets);
uint120 queued = SafeCast.toUint120(withdrawsQueued + assets);
uint40 claimTimestamp = uint40(block.timestamp + CLAIM_DELAY);

// Store the next withdrawal request
nextWithdrawalIndex = SafeCast.toUint128(requestId + 1);
nextWithdrawalIndex = SafeCast.toUint16(requestId + 1);
// Store the updated queued amount which reserves WETH in the withdrawal queue
withdrawsQueued = queued;
// Store requests
withdrawalRequests[requestId] = WithdrawalRequest({
withdrawer: msg.sender,
claimed: false,
claimTimestamp: claimTimestamp,
assets: SafeCast.toUint128(assets),
assets: SafeCast.toUint120(assets),
queued: queued
});

Expand All @@ -469,15 +467,15 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable {
/// @param requestId The index of the withdrawal request
/// @return assets The amount of liquidity assets that were transferred to the redeemer
function claimRedeem(uint256 requestId) external returns (uint256 assets) {
// Update the ARM's withdrawal queue's claimable amount
_updateWithdrawalQueueLiquidity();

// Load the structs from storage into memory
WithdrawalRequest memory request = withdrawalRequests[requestId];

require(request.claimTimestamp <= block.timestamp, "Claim delay not met");
// If there isn't enough reserved liquidity in the queue to claim
require(request.queued <= withdrawsClaimable, "Queue pending liquidity");
// Is there enough liquidity to claim this request?
require(
request.queued <= withdrawsClaimed + IERC20(liquidityAsset).balanceOf(address(this)),
"Queue pending liquidity"
);
require(request.withdrawer == msg.sender, "Not requester");
require(request.claimed == false, "Already claimed");

Expand All @@ -494,38 +492,11 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable {
IERC20(liquidityAsset).transfer(msg.sender, assets);
}

/// @dev Updates the claimable amount in the ARM's withdrawal queue.
/// That's the amount that is used to check if a request can be claimed or not.
function _updateWithdrawalQueueLiquidity() internal {
// Load the claimable amount from storage into memory
uint256 withdrawsClaimableMem = withdrawsClaimable;

// Check if the claimable amount is less than the queued amount
uint256 queueShortfall = withdrawsQueued - withdrawsClaimableMem;

// No need to do anything is the withdrawal queue is fully funded
if (queueShortfall == 0) {
return;
}

uint256 liquidityBalance = IERC20(liquidityAsset).balanceOf(address(this));

// Of the claimable withdrawal requests, how much is unclaimed?
// That is, the amount of the liquidity assets that is currently allocated for the withdrawal queue
uint256 allocatedLiquidity = withdrawsClaimableMem - withdrawsClaimed;

// If there is no unallocated liquidity assets then there is nothing to add to the queue
if (liquidityBalance <= allocatedLiquidity) {
return;
}

uint256 unallocatedLiquidity = liquidityBalance - allocatedLiquidity;

// the new claimable amount is the smaller of the queue shortfall or unallocated weth
uint256 addedClaimable = queueShortfall < unallocatedLiquidity ? queueShortfall : unallocatedLiquidity;

// Store the new claimable amount back to storage
withdrawsClaimable = SafeCast.toUint128(withdrawsClaimableMem + addedClaimable);
/// @notice Check if a withdrawal request can be claimed
/// @param requestId The index of the withdrawal request
function isClaimable(uint256 requestId) public view returns (bool) {
return
withdrawalRequests[requestId].queued <= withdrawsClaimed + IERC20(liquidityAsset).balanceOf(address(this));
}

/// @dev Calculate how much of the liquidity asset (WETH) in the ARM is not reserved for the withdrawal queue.
Expand Down
12 changes: 6 additions & 6 deletions test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ contract Fork_Concrete_LidoARM_ClaimRedeem_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.balanceOf(address(this)), 0);
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY);
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0);
assertEqQueueMetadata(DEFAULT_AMOUNT, 0, 0, 1);
assertEqQueueMetadata(DEFAULT_AMOUNT, 0, 1);
assertEqUserRequest(0, address(this), false, block.timestamp, DEFAULT_AMOUNT, DEFAULT_AMOUNT);

// Expected events
Expand All @@ -146,7 +146,7 @@ contract Fork_Concrete_LidoARM_ClaimRedeem_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.balanceOf(address(this)), 0);
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY);
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0);
assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, DEFAULT_AMOUNT, 1);
assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, 1);
assertEqUserRequest(0, address(this), true, block.timestamp, DEFAULT_AMOUNT, DEFAULT_AMOUNT);
assertEq(assets, DEFAULT_AMOUNT);
}
Expand Down Expand Up @@ -182,15 +182,15 @@ contract Fork_Concrete_LidoARM_ClaimRedeem_Test_ is Fork_Shared_Test_ {
(uint256 assets) = lidoARM.claimRedeem(0);

// Assertions after
assertApproxEqAbs(steth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY, 1);
assertApproxEqAbs(steth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY, 2);
assertEq(weth.balanceOf(address(lidoARM)), 0);
assertEq(lidoARM.outstandingEther(), 0);
assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees
assertEq(lidoARM.lastAvailableAssets(), int256(MIN_TOTAL_SUPPLY));
assertEq(lidoARM.balanceOf(address(this)), 0);
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY);
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0);
assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, DEFAULT_AMOUNT, 1);
assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, 1);
assertEqUserRequest(0, address(this), true, block.timestamp, DEFAULT_AMOUNT, DEFAULT_AMOUNT);
assertEq(assets, DEFAULT_AMOUNT);
}
Expand All @@ -214,7 +214,7 @@ contract Fork_Concrete_LidoARM_ClaimRedeem_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.balanceOf(address(this)), 0);
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY);
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0);
assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT / 2, 2);
assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT / 2, 2);
assertEqUserRequest(0, address(this), true, block.timestamp, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT / 2);
assertEqUserRequest(1, address(this), false, block.timestamp + delay, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT);

Expand All @@ -237,7 +237,7 @@ contract Fork_Concrete_LidoARM_ClaimRedeem_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.balanceOf(address(this)), 0);
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY);
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0);
assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, DEFAULT_AMOUNT, 2);
assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, 2);
assertEqUserRequest(0, address(this), true, block.timestamp - delay, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT / 2);
assertEqUserRequest(1, address(this), true, block.timestamp, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT);
assertEq(assets, DEFAULT_AMOUNT / 2);
Expand Down
28 changes: 14 additions & 14 deletions test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY, "total supply before"); // Minted to dead on deploy
assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY, "total assets before");
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), amount, "lp cap before");
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);

// Expected events
vm.expectEmit({emitter: address(weth)});
Expand All @@ -136,7 +136,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount, "total supply after");
assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount, "total assets after");
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0, "lp cap after"); // All the caps are used
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);
assertEq(shares, amount); // No perfs, so 1 ether * totalSupply (1e12) / totalAssets (1e12) = 1 ether
}

Expand All @@ -159,7 +159,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount); // Minted to dead on deploy
assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount);
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), amount);
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);

// Expected events
vm.expectEmit({emitter: address(weth)});
Expand All @@ -182,7 +182,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount * 2);
assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount * 2);
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); // All the caps are used
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);
assertEq(shares, amount); // No perfs, so 1 ether * totalSupply (1e18 + 1e12) / totalAssets (1e18 + 1e12) = 1 ether
}

Expand All @@ -206,7 +206,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount); // Minted to dead on deploy
assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount);
assertEq(liquidityProviderController.liquidityProviderCaps(alice), amount);
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);

// Expected events
vm.expectEmit({emitter: address(weth)});
Expand All @@ -230,7 +230,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount * 2);
assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount * 2);
assertEq(liquidityProviderController.liquidityProviderCaps(alice), 0); // All the caps are used
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);
assertEq(shares, amount); // No perfs, so 1 ether * totalSupply (1e18 + 1e12) / totalAssets (1e18 + 1e12) = 1 ether
}

Expand Down Expand Up @@ -261,7 +261,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
// 80% of the asset gain goes to the total assets
assertEq(lidoARM.totalAssets(), expectedTotalAssetsBeforeDeposit, "Total assets before");
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), DEFAULT_AMOUNT * 20, "lp cap before");
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);

uint256 depositedAssets = DEFAULT_AMOUNT * 20;
uint256 expectedShares = depositedAssets * MIN_TOTAL_SUPPLY / expectedTotalAssetsBeforeDeposit;
Expand Down Expand Up @@ -289,7 +289,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + expectedShares, "total supply after");
assertEq(lidoARM.totalAssets(), expectedTotalAssetsBeforeDeposit + depositedAssets, "Total assets after");
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0, "lp cap after"); // All the caps are used
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);
}

/// @notice Depositing into the ARM reserves WETH for the withdrawal queue.
Expand Down Expand Up @@ -326,7 +326,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY, "total supply before");
assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY, "total assets before");
assertEq(liquidityProviderController.liquidityProviderCaps(alice), DEFAULT_AMOUNT * 5, "lp cap before");
assertEqQueueMetadata(assetsRedeem, 0, 0, 1);
assertEqQueueMetadata(assetsRedeem, 0, 1);
assertApproxEqAbs(assetsRedeem, DEFAULT_AMOUNT, STETH_ERROR_ROUNDING, "assets redeem before");

uint256 amount = DEFAULT_AMOUNT * 2;
Expand Down Expand Up @@ -356,7 +356,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount, "total assets after");
assertEq(liquidityProviderController.liquidityProviderCaps(alice), DEFAULT_AMOUNT * 3, "alice cap after"); // All the caps are used
// withdrawal request is now claimable
assertEqQueueMetadata(assetsRedeem, 0, 0, 1);
assertEqQueueMetadata(assetsRedeem, 0, 1);
assertApproxEqAbs(shares, amount, STETH_ERROR_ROUNDING, "shares after"); // No perfs, so 1 ether * totalSupply (1e18 + 1e12) / totalAssets (1e18 + 1e12) = 1 ether
}

Expand Down Expand Up @@ -386,7 +386,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.lastAvailableAssets(), int256(MIN_TOTAL_SUPPLY), "last available assets before deposit");
assertEq(lidoARM.balanceOf(address(this)), 0); // Ensure no shares before
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), DEFAULT_AMOUNT);
assertEqQueueMetadata(0, 0, 0, 0);
assertEqQueueMetadata(0, 0, 0);

// Expected values = 1249998437501
// shares = assets * total supply / total assets
Expand Down Expand Up @@ -442,7 +442,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
);
assertEq(lidoARM.balanceOf(address(this)), 0, "User shares after redeem");
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0, "all user cap used");
assertEqQueueMetadata(receivedAssets, 0, 0, 1);
assertEqQueueMetadata(receivedAssets, 0, 1);

// 6. collect fees
lidoARM.collectFees();
Expand Down Expand Up @@ -500,7 +500,7 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.balanceOf(address(this)), 0); // Ensure no shares after
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); // Minted to dead on deploy
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); // All the caps are used
assertEqQueueMetadata(receivedAssets, 0, 0, 1);
assertEqQueueMetadata(receivedAssets, 0, 1);
assertEq(receivedAssets, DEFAULT_AMOUNT, "received assets");
}

Expand Down Expand Up @@ -569,6 +569,6 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ {
assertEq(lidoARM.balanceOf(address(this)), 0, "user shares after"); // Ensure no shares after
assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY, "total supply after"); // Minted to dead on deploy
assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0, "user cap"); // All the caps are used
assertEqQueueMetadata(receivedAssets, 0, 0, 1);
assertEqQueueMetadata(receivedAssets, 0, 1);
}
}
Loading

0 comments on commit 87ef2f5

Please sign in to comment.