diff --git a/src/EtherFiAdmin.sol b/src/EtherFiAdmin.sol index d00ee870..bd407633 100644 --- a/src/EtherFiAdmin.sol +++ b/src/EtherFiAdmin.sol @@ -248,7 +248,7 @@ contract EtherFiAdmin is Initializable, OwnableUpgradeable, UUPSUpgradeable { for (uint256 i = 0; i < _report.withdrawalRequestsToInvalidate.length; i++) { withdrawRequestNft.invalidateRequest(_report.withdrawalRequestsToInvalidate[i]); } - withdrawRequestNft.finalizeRequests(_report.lastFinalizedWithdrawalRequestId); + withdrawRequestNft.finalizeRequests(_report.lastFinalizedWithdrawalRequestId, _report.finalizedWithdrawalAmount); } function slotForNextReportToProcess() public view returns (uint32) { diff --git a/src/WithdrawRequestNFT.sol b/src/WithdrawRequestNFT.sol index f129a8a5..8086ff30 100644 --- a/src/WithdrawRequestNFT.sol +++ b/src/WithdrawRequestNFT.sol @@ -85,8 +85,11 @@ contract WithdrawRequestNFT is ERC721Upgradeable, UUPSUpgradeable, OwnableUpgrad function initializeV2dot5(address _roleRegistry) external onlyOwner { require(address(roleRegistry) == address(0x00), "already initialized"); - DEPRECATED_accumulatedDustEEthShares = 0; // TODO: compile list of values in DEPRECATED_admins to clear out + DEPRECATED_accumulatedDustEEthShares = 0; + + // All requests must be refinalized with the new withdrawal flow + lastFinalizedRequestId = 0; roleRegistry = RoleRegistry(_roleRegistry); finalizationCheckpoints.push(FinalizationCheckpoint(0, 0)); @@ -148,21 +151,21 @@ contract WithdrawRequestNFT is ERC721Upgradeable, UUPSUpgradeable, OwnableUpgrad emit WithdrawRequestSeized(requestId); } + /// @notice finalizes a batch of requests and locks the corresponding ETH to be withdrawn + /// @dev called by the `EtherFiAdmin` contract to finalize a batch of requests based on the last oracle report function finalizeRequests(uint32 lastRequestId) external { if (!roleRegistry.hasRole(WITHDRAW_NFT_ADMIN_ROLE, msg.sender)) revert IncorrectRole(); uint256 totalAmount = uint256(calculateTotalPendingAmount(lastRequestId)); - uint256 cachedSharePrice = liquidityPool.amountForShare(E27_PRECISION_BASE); - - finalizationCheckpoints.push(FinalizationCheckpoint(uint32(lastRequestId), cachedSharePrice)); - - lastFinalizedRequestId = lastRequestId; + _finalizeRequests(lastRequestId, totalAmount); + } - if (totalAmount > 0) { - liquidityPool.withdraw(address(this), totalAmount); - } + /// @notice `finalizeRequests` with the ability to specify the total amount of ETH to be locked + /// @dev The oracle calculates the amount of ETH that is needed to fulfill the pending withdrawal off-chain + function finalizeRequests(uint256 lastRequestId, uint256 totalAmount) external { + if (!roleRegistry.hasRole(WITHDRAW_NFT_ADMIN_ROLE, msg.sender)) revert IncorrectRole(); - emit UpdateFinalizedRequestId(lastRequestId, totalAmount); + _finalizeRequests(lastRequestId, totalAmount); } function invalidateRequest(uint32 requestId) external { @@ -302,7 +305,6 @@ contract WithdrawRequestNFT is ERC721Upgradeable, UUPSUpgradeable, OwnableUpgrad return finalizationCheckpoints[checkpointId]; } - function getRequest(uint32 requestId) external view returns (IWithdrawRequestNFT.WithdrawRequest memory) { return _requests[requestId]; } @@ -342,6 +344,19 @@ contract WithdrawRequestNFT is ERC721Upgradeable, UUPSUpgradeable, OwnableUpgrad emit WithdrawRequestClaimed(requestId, amountToWithdraw, 0, recipient); } + function _finalizeRequests(uint256 lastRequestId, uint256 totalAmount) internal { + uint256 cachedSharePrice = liquidityPool.amountForShare(E27_PRECISION_BASE); + finalizationCheckpoints.push(FinalizationCheckpoint(uint32(lastRequestId), cachedSharePrice)); + + lastFinalizedRequestId = uint32(lastRequestId); + + if (totalAmount > 0) { + liquidityPool.withdraw(address(this), totalAmount); + } + + emit UpdateFinalizedRequestId(uint32(lastRequestId), totalAmount); + } + // invalid NFTs is non-transferable except for the case they are being burnt by the owner via `seizeInvalidRequest` function _beforeTokenTransfer(address /*from*/, address /*to*/, uint256 firstTokenId, uint256 batchSize) internal view override { for (uint256 i = 0; i < batchSize; i++) { diff --git a/src/interfaces/IWithdrawRequestNFT.sol b/src/interfaces/IWithdrawRequestNFT.sol index 96a3fd07..5268c8d6 100644 --- a/src/interfaces/IWithdrawRequestNFT.sol +++ b/src/interfaces/IWithdrawRequestNFT.sol @@ -23,4 +23,5 @@ interface IWithdrawRequestNFT { function invalidateRequest(uint32 requestId) external; function finalizeRequests(uint32 lastRequestId) external; + function finalizeRequests(uint256 lastRequestId, uint256 totalAmount) external; } diff --git a/test/WithdrawRequestNFT.t.sol b/test/WithdrawRequestNFT.t.sol index b38dfa63..5ae5ba36 100644 --- a/test/WithdrawRequestNFT.t.sol +++ b/test/WithdrawRequestNFT.t.sol @@ -429,15 +429,11 @@ contract WithdrawRequestNFTTest is TestSetup { assertEq(address(chad).balance, chadBalance + claimableAmount, "Chad should receive the claimable amount"); } - function test_updated_checkpoint_logic() public payable { - for (uint256 i = 0; i < 100; i++) { + function test_updated_checkpoint_logic() public { + for (uint256 i = 0; i < 50; i++) { address user = vm.addr(i + 1); users.push(user); vm.deal(user, 15 ether); - } - - // first 50 users deposit - for (uint256 i = 0; i < 50; i++) { vm.prank(users[i]); liquidityPoolInstance.deposit{value: 1 ether}(); }