Liquidation Keepers Automatically Become eUSD Debt Providers for Other Liquidations.
One of the most important mechanisms in the Lybra Protocol is the liquidation of poorly collateralized vaults. For example, if a vault is found to have a collateralization ratio that is too small, a liquidator may provide debt tokens to the protocol and retrieve the vault collateral at a discount:\\n```\\nfunction liquidation(address provider, address onBehalfOf, uint256 assetAmount) external virtual {\\n uint256 assetPrice = getAssetPrice();\\n uint256 onBehalfOfCollateralRatio = (depositedAsset[onBehalfOf] \\* assetPrice \\* 100) / borrowed[onBehalfOf];\\n require(onBehalfOfCollateralRatio < badCollateralRatio, "Borrowers collateral ratio should below badCollateralRatio");\\n\\n require(assetAmount \\* 2 <= depositedAsset[onBehalfOf], "a max of 50% collateral can be liquidated");\\n require(EUSD.allowance(provider, address(this)) != 0, "provider should authorize to provide liquidation EUSD");\\n uint256 eusdAmount = (assetAmount \\* assetPrice) / 1e18;\\n\\n \\_repay(provider, onBehalfOf, eusdAmount);\\n uint256 reducedAsset = assetAmount \\* 11 / 10;\\n totalDepositedAsset -= reducedAsset;\\n depositedAsset[onBehalfOf] -= reducedAsset;\\n uint256 reward2keeper;\\n if (provider == msg.sender) {\\n collateralAsset.safeTransfer(msg.sender, reducedAsset);\\n } else {\\n reward2keeper = (reducedAsset \\* configurator.vaultKeeperRatio(address(this))) / 110;\\n collateralAsset.safeTransfer(provider, reducedAsset - reward2keeper);\\n collateralAsset.safeTransfer(msg.sender, reward2keeper);\\n }\\n emit LiquidationRecord(provider, msg.sender, onBehalfOf, eusdAmount, reducedAsset, reward2keeper, false, block.timestamp);\\n}\\n```\\n\\nTo liquidate the vault, the liquidator needs to transfer debt tokens from the provider address, which in turn needs to have had approved allowance of the token for the vault:\\n```\\nrequire(EUSD.allowance(provider, address(this)) != 0, "provider should authorize to provide liquidation EUSD");\\n```\\n\\nThe allowance doesn't need to be large, it only needs to be non-zero. While it is true that in the `superLiquidation` function the allowance check is for `eusdAmount`, which is the amount associated with `assetAmount` (the requested amount of collateral to be liquidated), the liquidator could simply call the maximum of the allowance the provider has given to the vault and then repeat the liquidation process. The allowance does not actually decrease throughout the liquidation process.\\n```\\nrequire(EUSD.allowance(provider, address(this)) >= eusdAmount, "provider should authorize to provide liquidation EUSD");\\n```\\n\\nNotably, this address doesn't have to be the same one as the liquidator. In fact, there are no checks on whether the liquidator has an agreement or allowance from the provider to use their tokens in this particular vault's liquidation. The contract only checks to see if the provider has `EUSD` allowance for the vault, and how to split the rewards if the provider is different from the liquidator:\\n```\\nif (provider == msg.sender) {\\n collateralAsset.safeTransfer(msg.sender, reducedAsset);\\n} else {\\n reward2keeper = (reducedAsset \\* configurator.vaultKeeperRatio(address(this))) / 110;\\n collateralAsset.safeTransfer(provider, reducedAsset - reward2keeper);\\n collateralAsset.safeTransfer(msg.sender, reward2keeper);\\n}\\n```\\n\\nIn fact, this is a design choice of the system to treat the allowance to the vault as an agreement to become a public provider of debt tokens for the liquidation process. It is important to note that there are incentives associated with being a provider as they get the collateral asset at a discount.\\nHowever, it is not obvious from documentation at the time of the audit nor the code that an address having a non-zero `EUSD` allowance for the vault automatically allows other users to use that address as a provider. Indeed, many general-purpose liquidator bots use their tokens during liquidations, using the same address for both the liquidator and the provider. As a result, this would put that address at the behest of any other user who would want to utilize these tokens in liquidations. The user might not be comfortable doing this trade in any case, even at a discount.\\nIn fact, due to this mechanism, even during consciously initiated liquidations MEV bots could spot this opportunity and front-run the liquidator's transaction. A frontrunner could put themselves as the keeper and the original user as the provider, grabbing the `reward2keeper` fee and leaving the original address with fewer rewards and failed gas after the liquidation.
While the mechanism is understood to be done for convenience and access to liquidity as a design decision, this could put unaware users in unfortunate situations of having performed a trade without explicit consent. Specifically, the MEV attack vector could be executed and repeated without fail by a capable actor monitoring the mempool. Consider having a separate, explicit flag for allowing others to use a user's tokens during liquidation, thus also accommodating solo liquidators by removing the MEV attack vector. Consider explicitly mentioning these mechanisms in the documentation as well.
```\\nfunction liquidation(address provider, address onBehalfOf, uint256 assetAmount) external virtual {\\n uint256 assetPrice = getAssetPrice();\\n uint256 onBehalfOfCollateralRatio = (depositedAsset[onBehalfOf] \\* assetPrice \\* 100) / borrowed[onBehalfOf];\\n require(onBehalfOfCollateralRatio < badCollateralRatio, "Borrowers collateral ratio should below badCollateralRatio");\\n\\n require(assetAmount \\* 2 <= depositedAsset[onBehalfOf], "a max of 50% collateral can be liquidated");\\n require(EUSD.allowance(provider, address(this)) != 0, "provider should authorize to provide liquidation EUSD");\\n uint256 eusdAmount = (assetAmount \\* assetPrice) / 1e18;\\n\\n \\_repay(provider, onBehalfOf, eusdAmount);\\n uint256 reducedAsset = assetAmount \\* 11 / 10;\\n totalDepositedAsset -= reducedAsset;\\n depositedAsset[onBehalfOf] -= reducedAsset;\\n uint256 reward2keeper;\\n if (provider == msg.sender) {\\n collateralAsset.safeTransfer(msg.sender, reducedAsset);\\n } else {\\n reward2keeper = (reducedAsset \\* configurator.vaultKeeperRatio(address(this))) / 110;\\n collateralAsset.safeTransfer(provider, reducedAsset - reward2keeper);\\n collateralAsset.safeTransfer(msg.sender, reward2keeper);\\n }\\n emit LiquidationRecord(provider, msg.sender, onBehalfOf, eusdAmount, reducedAsset, reward2keeper, false, block.timestamp);\\n}\\n```\\n