Vulnerability: Large amounts of points can be minted virtually without any cost
Severity: medium

Description:
Large amounts of points can be minted virtually without any cost. The points are intended to be used to exchange something of value. A malicious user could abuse this to obtain a large number of points, which could obtain excessive value and create unfairness among other protocol users.\\nWhen depositing stable collateral, the LPs only need to pay for the keeper fee. The keeper fee will be sent to the caller who executed the deposit order.\\nWhen withdrawing stable collateral, the LPs need to pay for the keeper fee and withdraw fee. However, there is an instance where one does not need to pay for the withdrawal fee. Per the condition at Line 120 below, if the `totalSupply` is zero, this means that it is the final/last withdrawal. In this case, the withdraw fee will not be applicable and remain at zero.\\n```\\nFile: StableModule.sol\\n    function executeWithdraw(\\n        address _account,\\n        uint64 _executableAtTime,\\n        FlatcoinStructs.AnnouncedStableWithdraw calldata _announcedWithdraw\\n    ) external whenNotPaused onlyAuthorizedModule returns (uint256 _amountOut, uint256 _withdrawFee) {\\n        uint256 withdrawAmount = _announcedWithdraw.withdrawAmount;\\n..SNIP..\\n        _burn(_account, withdrawAmount);\\n..SNIP..\\n        // Check that there is no significant impact on stable token price.\\n        // This should never happen and means that too much value or not enough value was withdrawn.\\n        if (totalSupply() > 0) {\\n            if (\\n                stableCollateralPerShareAfter < stableCollateralPerShareBefore - 1e6 ||\\n                stableCollateralPerShareAfter > stableCollateralPerShareBefore + 1e6\\n            ) revert FlatcoinErrors.PriceImpactDuringWithdraw();\\n\\n            // Apply the withdraw fee if it's not the final withdrawal.\\n            _withdrawFee = (stableWithdrawFee * _amountOut) / 1e18;\\n\\n            // additionalSkew = 0 because withdrawal was already processed above.\\n            vault.checkSkewMax({additionalSkew: 0});\\n        } else {\\n            // Need to check there are no longs open before allowing full system withdrawal.\\n            uint256 sizeOpenedTotal = vault.getVaultSummary().globalPositions.sizeOpenedTotal;\\n\\n            if (sizeOpenedTotal != 0) revert FlatcoinErrors.MaxSkewReached(sizeOpenedTotal);\\n            if (stableCollateralPerShareAfter != 1e18) revert FlatcoinErrors.PriceImpactDuringFullWithdraw();\\n        }\\n```\\n\\nWhen LPs deposit rETH and mint UNIT, the protocol will mint points to the depositor's account as per Line 84 below.\\nAssume that the vault has been newly deployed on-chain. Bob is the first LP to deposit rETH into the vault. Assume for a period of time (e.g., around 30 minutes), there are no other users depositing into the vault except for Bob.\\nBob could perform the following actions to mint points for free:\\nBob announces a deposit order to deposit 100e18 rETH. Paid for the keeper fee. (Acting as a LP).\\nWait 10 seconds for the `minExecutabilityAge` to pass\\nBob executes the deposit order and mints 100e18 UNIT (Exchange rate 1:1). Protocol also mints 100e18 points to Bob's account. Bob gets back the keeper fee. (Acting as Keeper)\\nImmediately after his `executeDeposit` TX, Bob inserts an "announce withdraw order" TX to withdraw all his 100e18 UNIT and pay for the keeper fee.\\nWait 10 seconds for the `minExecutabilityAge` to pass\\nBob executes the withdraw order and receives back his initial investment of 100e18 rETH. Since he is the only LP in the protocol, it is considered the final/last withdrawal, and he does not need to pay any withdraw fee. He also got back his keeper fee. (Acting as Keeper)\\nEach attack requires 20 seconds (10 + 10) to be executed. Bob could rinse and repeat the attack until he was no longer the only LP in the system, where he had to pay for the withdraw fee, which might make this attack unprofitable.\\nIf Bob is the only LP in the system for 30 minutes, he could gain 9000e18 points ((30 minutes / 20 seconds) * 100e18 ) for free as Bob could get back his keeper fee and does not incur any withdraw fee. The only thing that Bob needs to pay for is the gas fee, which is extremely cheap on L2 like Base.\\n```\\nFile: StableModule.sol\\n    function executeDeposit(\\n        address _account,\\n        uint64 _executableAtTime,\\n        FlatcoinStructs.AnnouncedStableDeposit calldata _announcedDeposit\\n    ) external whenNotPaused onlyAuthorizedModule returns (uint256 _liquidityMinted) {\\n        uint256 depositAmount = _announcedDeposit.depositAmount;\\n..SNIP..\\n        _liquidityMinted = (depositAmount * (10 ** decimals())) / stableCollateralPerShare(maxAge);\\n..SNIP..\\n        _mint(_account, _liquidityMinted);\\n\\n        vault.updateStableCollateralTotal(int256(depositAmount));\\n..SNIP..\\n        // Mint points\\n        IPointsModule pointsModule = IPointsModule(vault.moduleAddress(FlatcoinModuleKeys._POINTS_MODULE_KEY));\\n        pointsModule.mintDeposit(_account, _announcedDeposit.depositAmount);\\n```\\n

Impact:
Large amounts of points can be minted virtually without any cost. The points are intended to be used to exchange something of value. A malicious user could abuse this to obtain a large number of points, which could obtain excessive value from the protocol and create unfairness among other protocol users.

Recommended mitigation:
One approach that could mitigate this risk is also to impose withdraw fee for the final/last withdrawal so that no one could abuse this exception to perform any attack that was once not profitable due to the need to pay withdraw fee.\\nIn addition, consider deducting the points once a position is closed or reduced in size so that no one can attempt to open and adjust/close a position repeatedly to obtain more points.