diff --git a/audits/ChainSecurity_preliminary_findings.txt b/audits/ChainSecurity_preliminary_findings.txt deleted file mode 100644 index dc7254b6..00000000 --- a/audits/ChainSecurity_preliminary_findings.txt +++ /dev/null @@ -1,63 +0,0 @@ -ChainSecurity preliminary findings in EVK audit - -THIS IS WORK IN PROGRESS - -1. We think that the liquidation mechanism can be missused in cases where the LTV for a collateral is high. Instead of performing a single liquidation, the liquidator can perform many partial liquidations which gives him more reward then the single liquidation. Thus, more bad debt than expected is created in the vault which is equal to the additional reward earned by the liquidator when liquidating a position partially multiple times instead of all at once. -For example: -If the violator has 85 units of collateral with 0.8 LTV and 80 units of debt. A liquidator can liquidate the position all at once with a discount factor of 0.85. The liquidator will receive 72.25 units of debt and 85 units of collateral thus making 12.75 in the process. The violator's position will end up with 7.75 (bad) debt. However, the liquidator can decide to perform the liquidation in two steps, liquidating half of the position first and then the other half. The liquidator will end up with 36.125 + 34 debt and 42.5 + 42.5 collateral, resulting in a profit of 14.875. The violator's position will at the end contain 9.875 debt which is more debt than if the liquidation would have been performed at once. The difference was earned by the liquidator. - -Preliminary response from Euler: - -We have made a set of 3 changes to the liquidation system in order to mitigate the issues discovered by our auditors. -The first issue raised is related to the "Counterproductive Incentives" issue described by OpenZeppelin in their [2019 Compound audit](https://blog.openzeppelin.com/compound-audit). Liquidation systems that incentivise liquidators with extra collateral value as a bonus (or discount) can, in some circumstances, leave violators *more* unhealthy than they were pre-liquidation. In the Euler system, the discount is proportional to how unhealthy the user is, which means that in these cases, a liquidator may improve their total yield by performing many small liquidations, rather than one large liquidation. Each smaller liquidation will decrease the user's health and therefore increase their discount for subsequent liquidations, up until the maximum liquidation discount is reached. As described in our [Dutch Liquidation Analysis](https://docs.euler.finance/Dutch_Liquidation_Analysis.pdf) research paper, this scenario can be avoided by selecting an appropriately low maximum discount factor. -Change 1: With this in mind, we have added EVK functionality that allows governors to configure the vault's maximum discount factor. In many cases, governors will compute an appropriate maximum discount based on the highest configured LTV for the vault, although there may be other considerations involved. A governor must specify a value for this parameter, otherwise the liquidation system will not function properly. -The second issue raised is a general observation that price manipulation can be used to attack lending markets, and that some of the oracles we would like to support have special challenges. In particular, pull-based oracles like Pyth and Redstone provide more flexibility to attackers because they can typically choose to use any published prices within an N-minute window. For example, an attacker may be monitoring prices off-chain, waiting for a large decline in the price of a vault's collateral asset (or, equivalently, a large increase in the price of the liability asset). If the decline is sufficiently large, the attacker will search the previous N-minutes of prices and select the pair with the largest difference. The attacker will then submit a transaction that performs the following attack: -* Updates the oracle with the old price -* Deposits collateral and borrows as much as possible -* Updates the oracle with the new price, causing the position to become very unhealthy -* Liquidates the position from another separate account, leaving bad debt. This bad debt corresponds to profit from the attack at the expense of the vault's depositors -Although impossible to solve in the general case, to reduce the impact of this issue we have have made two modifications to the EVK: -Change 2: We now allow the governor to configure separate borrowing and liquidation LTVs. This requires the attacker to find correspondingly larger price jumps. -Change 3: We have added a "cool-off period" where an account cannot be liquidated. Cool-off periods begin once an account has successfully passed an account status check, and last a governor-configurable number of seconds. By setting a non-zero cool-off period, accounts cannot be liquidated inside a block that they were previously healthy. -The consequence of this is that the attack described above can no longer be done in an entirely risk-free manner. The position needs to be setup in one block but liquidated in a following block, potentially opening up the opportunity for other unrelated parties to perform the liquidation instead of the attacker. Additionally, such attacks cannot be financed with flash loans. As well as price-oracle related attacks, this protection may also reduce the impact of future unknown protocol attacks. -More generally, the cool-off period allows a vault creator to express a minimum-expected liveness period for a particular chain. If the maximum possible censorship time can be estimated, the cool-off period can be configured larger than this, with the trade-off being that legitimate liquidations of new positions may be delayed by this period of time. - -2. In Governance.sol we noted that if convertFees has not been called in a while and then the protocolFee is modified that the new fee distribution between the governor and the protocol will be applied retroactively to the unclaimed fees. - -Comment from Euler: Acknowledged. - -3. We found that deposit() and redeem() in Vault are not ERC-4626 compatible as they replace amount by the maximum available amount if it is equal to type(uint256).max. We noticed that a list of incompatibilities between the EVK and ERC-4626 vaults is available in the EVK white paper but the deposit() and redeem() incompatibility is not mentionned there. - -Comment from Euler: Acknowledged, white paper updated - -4. The specification for Synths stipulates that : "Euler synthetic vaults are a special configuration of vaults which use hooks to disable deposit, mint and loop for all addresses except the synth address itself, disallowing deposits from normal users". However, it would still be possible for any user to invoke skim(), allowing the user to deposit funds into the synthetic vault and receive shares. - -Comment from Euler: Acknowledged, white paper update - - -5. We noticed that in initVaultCache, on line 100 in Cache that the computation of newTotalShares does not make use of VIRTUAL_DEPOSIT_AMOUNT used for every conversion between shares and assets. We calculated that the current computation results in less shares created for fees. We are wondering if this is a voluntary decision and if yes we would be interested to know the motivation behind it. - -Comment from Euler: Acknowledged. - -6. In LiquidityUtils.sol, in checkLiquidity a violator can enable on purpose the maximum amount of collaterals (10) to increase the amount of gas a liquidator has to spend if he wants to liquidate the violator. - -Comment from Euler: For this reason we are limiting the amount of collaterals to a relatively small number of 10. Note that the vault's creator/governor should be take the gas costs into account when setting LTVs. Some collaterals might have expensive balanceOf functions (in the future, with multiple vault implementations) or be expensive to price. Just setting a list of collaterals to 10 without any LTVs configured would cost 20k to cold read LTV config slots. With overhead it's still very acceptable. The max number of collaterals could be reduced further per controller vault by using hooks. - -7. There is a rounding issue in Liquidations.sol line 75 : -liqCache.liability = getCurrentOwed(vaultCache, violator).toAssetsUp() -The liability amount is used to compute how much collateral the liquidator will get and it is rounded up. Therefore, if the liability is 1.01 wei (1 wei + dust), it will round to 2. However, when the liability is transfered (line 177) it will be 1.01. But the liquidator's reward will be computed as if he took over 2 debt - -Comment from Euler: Even though the internal debt would be 1.01, it's not possible to repay it other than by providing 2 whole wei of assets (or equivalent shares in case of deloop). The liquidator could maybe bundle multiple such accounts, in this case up to 99 and repay 100 wei for an actual 99.99 debt. In theory it's a seizable saving, but probably unrealistic in practice. - -Comment from CS: While we understand your explanation, we would like to point out the fact that a liquidator will be able to pay back 0.99 of his own debt for free. - -Preliminary resolution from Euler: Acknowledged. The security and business impact is negligible. - - -8. In conversionTotals, partial interest is always rounded up using toAssetsUp() to compute totalAssets. This can producde a rounding in the wrong direction in Shares.toAssetsDown() and Assets.toSharesUp. For example, if there are 10^6 (virtual assets) + 1.1 (partial interest accrued), and shares amount is 10^6 (virtual shares) + 2 then toAssetsDown(1) will return 1 instead of 0. - -Comment from Euler: A similar reasoning can be applied as in the previous point. Although the actual debt is 1.1, the last borrower (in general case) to repay their debt would need to provide whole 2 wei. This time the totalBorrows are a single value so there is no way to get rid of 1.1 borrows for less than 2 wei. - -Comment from CS: We understand your explanation but would like to point out that this will allow a user to redeem() 1 wei more in certain cases. For example, amount to redeem is 1e6, shares is 2e6 and assets is (2e6-0.99). The user should receive 1e6 - 1 but will get 1e6. - -Preliminary resolution from Euler: Acknowledged. The security and business impact is negligible. \ No newline at end of file