Vulnerability: Unbounded loop in Controller.calcAccountEquity allows DoS on liquidation
Severity: high

Description:
`Controller.calcAccountEquity` calculates the relative value of a user's supplied collateral and their active borrow positions. Users may mark an arbitrary number of assets as collateral, and may borrow from an arbitrary number of assets. In order to calculate the value of both of these positions, this method performs two loops.\\nFirst, to calculate the sum of the value of a user's collateral:\\n```\\n// Calculate value of all collaterals\\n// collateralValuePerToken = underlyingPrice \\* exchangeRate \\* collateralFactor\\n// collateralValue = balance \\* collateralValuePerToken\\n// sumCollateral += collateralValue\\nuint256 \\_len = \\_accountData.collaterals.length();\\nfor (uint256 i = 0; i < \\_len; i++) {\\n    IiToken \\_token = IiToken(\\_accountData.collaterals.at(i));\\n```\\n\\nSecond, to calculate the sum of the value of a user's borrow positions:\\n```\\n// Calculate all borrowed value\\n// borrowValue = underlyingPrice \\* underlyingBorrowed / borrowFactor\\n// sumBorrowed += borrowValue\\n\\_len = \\_accountData.borrowed.length();\\nfor (uint256 i = 0; i < \\_len; i++) {\\n    IiToken \\_token = IiToken(\\_accountData.borrowed.at(i));\\n```\\n\\nFrom dForce, we learned that 200 or more assets would be supported by the Controller. This means that a user with active collateral and borrow positions on all 200 supported assets could force any `calcAccountEquity` action to perform some 400 iterations of these loops, each with several expensive external calls.\\nBy modifying dForce's unit test suite, we showed that an attacker could force the cost of `calcAccountEquity` above the block gas limit. This would prevent all of the following actions, as each relies on calcAccountEquity:\\n`iToken.transfer` and `iToken.transferFrom`\\n`iToken.redeem` and `iToken.redeemUnderlying`\\n`iToken.borrow`\\n`iToken.liquidateBorrow` and `iToken.seize`\\nThe following actions would still be possible:\\n`iToken.mint`\\n`iToken.repayBorrow` and `iToken.repayBorrowBehalf`\\nAs a result, an attacker may abuse the unbounded looping in `calcAccountEquity` to prevent the liquidation of underwater positions. We provided dForce with a PoC here: gist.

Impact:

Recommended mitigation:
There are many possible ways to address this issue. Some ideas have been outlined below, and it may be that a combination of these ideas is the best approach:\\nIn general, cap the number of markets and borrowed assets a user may have: The primary cause of the DoS is that the number of collateral and borrow positions held by a user is only restricted by the number of supported assets. The PoC provided above showed that somewhere around 150 collateral positions and 150 borrow positions, the gas costs of `calcAccountEquity` use most of the gas in a block. Given that gas prices often spike along with turbulent market conditions and that liquidations are far more likely in turbulent market conditions, a cap on active markets / borrows should be much lower than 150 each so as to keep the cost of liquidations as low as possible.\\ndForce should perform their own gas cost estimates to determine a cap, and choose a safe, low value. Estimates should be performed on the high-level `liquidateBorrow` method, so as to simulate an actual liquidation event. Additionally, estimates should factor in a changing block gas limit, and the possibility of opcode gas costs changing in future forks. It may be wise to make this cap configurable, so that the limits may be adjusted for future conditions.