What You Should Know Before Putting Half a Million DAI in Compound
[EDIT 6/30/20] — This post is no longer relevant. Shortly after this post, Compound updated their admin to be a multi-sig with a time delay, as I suggested. More recently, they have transferred full control of their governance to the COMP token holders, with a minimum 2-day grace period before any accepted protocol changes are implemented. The admin capabilities, now firmly in the hands of the COMP token holder community, no longer represent a centralized point of failure for the Compound protocol.
You’ve probably heard of Compound. They built compound.finance on Ethereum which allows you to lend and earn interest on your ETH, DAI, USDC, and several other ERC20s.
Today, the interest rate offered to DAI lenders is 10%, which is high enough to turn EthHeads’ heads (see cover photo).
As the CEO of SpankChain, it’s my job to manage the company reserves, which also includes nearly half a million DAI. At 10% annual interest, that’s ~$4,000 per month that we’re leaving on the table by not moving our DAI into Compound. That’s quite the opportunity cost. But the thing to remember when investing is that there is no free lunch. All investments have their risks and lending on Compound is no exception.
I spent some time over the last month evaluating several categories of risk when lending on Compound:
- Contract Security Risks
- Centralized Points of Failure
- Bank Run Risk
I break my investigation down by category below, but the most important things to know are:
- The smart contract security seems legit.
- Compound is a CUSTODIAL system, all lending pools can be trivially drained if their admin private key is compromised.
- When you lend on Compound, you are NOT guaranteed to be able to withdraw whenever you want. If you try to withdraw your funds and all the money is locked up in outstanding loans, your withdrawal transaction will fail.
Compound has been audited by several reputable smart contract security firms.
In addition, Compound was offering a private bug bounty of up to $250,000 for critical vulnerabilities (defined as 1% of funds stolen or 10% frozen), and to my knowledge no independent security researchers have been able to claim the bounty.
The contracts have also held $20M+ for over 6 months, $50M+ for over 2 months, and currently hold $100M+. For me personally, the most important metric of contract security is total funds held in contract * time held in contract, and Compound has been secure with quite a large public bounty thus far.
Based on the above factors, I presently believe that the Compound smart contracts are secure.
Centralized Points of Failure
I’m not a smart contract security expert myself, so I enlisted the assistance of the one and only samczsun who famously found a critical bug in the 0x contracts (despite multiple audits from top firms) and was paid $100,000 for it. He had the following to report about centralized points of failure in Compound (emphasis mine):
Compound v2 has four different administrative positions which are set to three distinct addresses:
- Each cToken has an administrator. Currently, that’s set to 0x8B8592E9570E96166336603a1b4bd1E8Db20fa20 for all of them
- Each cToken also has a comptroller, which are currently all set to 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B. This Unitroller also has an administrator, which is set to 0x8B8592E9570E96166336603a1b4bd1E8Db20fa20
- The current price oracle has both an anchor admin and a poster, set to 0xF06e41aDD8A7E7A8aD81a07C0ACA291E4573ca50 and 0x3c6809319201b978D821190Ba03fA19A3523BD96 respectively.
With administrative access to a cToken, an attacker could replace the comptroller implementation, which would allow them to do one or more of the following:
- Prevent transferring existing cTokens, by returning false from transferAllowed
- Transfer cTokens which are held as collateral, by returning true from transferAllowed
- Prevent minting new cTokens, by returning false from mintAllowed
- Prevent redeeming existing cTokens, by returning false from redeemAllowed
- Prevent repayment of existing borrows, by returning false from repayBorrowAllowed
- Prevent liquidating borrows, by returning false from liquidateBorrowAllowed
- Steal a user’s cTokens, by returning true from seizeAllowed
- Prevent borrowing the underlying asset, by returning false from borrowAllowed
- Drain all of the underlying asset, by returning true from borrowAllowed
With administrative access to a cToken, an attacker could also replace the interest rate model, which would allow them to do the following:
- Increase the borrow rate to 0.0005% / block (i.e. the interest rate per block)
With administrative access to the current Unitroller proxy, an attacker could:
- Replace the implementation of the Unitroller, which would allow them to perform the same attacks if they could replace the comptroller of a cToken, but for all cTokens which use the Unitroller (which is 100% of them)
- Change the liquidation incentive, which would allow attackers to seize a disproportionate amount of tokens when liquidating
- Change the price oracle, which would allow the attacker to feed low prices for existing cTokens (such as ETH, WBTC, etc) and borrow against that low price
- Change the collateral factor for a cToken, which when combined with the ability to add a new cToken and the ability to change the price oracle, would allow the attacker to drain all assets by borrowing against a token they create
With anchor admin access to the price oracle, an attacker could:
- Deviate the price of an asset by 10% from its true value
With poster access to the price oracle, an attacker could:
- Deviate the price of an asset by 10% from its stored value every hour
With both anchor admin and poster access to the price oracle, an attacker could:
- Set the price of an asset to an arbitrary value
To summarize samczsun’s report: The Compound Protocol is designed to be able to be upgraded in place by a central administrator. The important contracts are proxies which simply point to a separate contract address which holds their implementation logic, and the administrator has the privilege of changing these address pointers at will. Because all cTokens use the same administrator, if the administrator key is compromised, all assets deposited in Compound can be trivially drained.
There are a few other more sneaky attacks that sam mentions above as well, but chances are an attacker would—given the option—sooner run away with all the funds than pursue more complex attacks.
OpenZeppelin helpfully covered this in their Compound Audit Summary.
However, in the hands of a malicious or compromised administrator, these privileges contain the ability to trivially freeze markets, censor transactions or steal all assets from the system. Similarly, control of the price feed can be used to steal most, if not all, assets from the system. Currently, the same externally owned account is the administrator for all live markets.
Interestingly, no mention of this was made in any of the materials produced by the Trail of Bits team. Furthermore, the Compound FAQ also casually understates the privileges of the admin and provides no warning of its ability to drain all funds:
Compound Labs, Inc., the developer of the protocol, currently controls the Ethereum address
0x8b8592e9570e96166336603a1b4bd1e8db20fa20, which is the protocol admin. The admin address has the right to support additional assets, upgrade the price feed oracle, upgrade the interest rate models, and upgrade the risk model of the protocol.
Another thing to note is that Compound’s current custodial setup does not by itself make their system insecure. They are highly motivated to keep the admin key safe, and probably (hopefully) are working with the best custody providers their $8.2M a16z led seed round can buy. It certainly is, however, something I’m keeping in mind when deciding to deposit half a million DAI.
Bank Run Risk
This tweet from the COO of Dharma, a formerly competing lending platform led me down a rabbit hole to figure out what the bank run risk looks like in Compound.
The utilization rate in the quoted tweet is 98.62%, which meant that at the time, 98.62% of the DAI deposited by lenders was being loaned out to borrowers. Only 1.38% of the DAI was available for withdrawals, so only a small fraction of the lenders would be able to recover their DAI if they wanted to.
If enough DAI lenders (cDAI holders) wanted their DAI back at the same time, their withdrawals might exhaust the available DAI, increasing the utilization rate to 100% and preventing any further withdrawals. Lenders attempting to withdraw would simply see their transactions fail, and would be forced to wait until more borrowers paid back their loans before they could withdraw.
Because the possibility of getting stuck with cDAI exists, people will worry about it, and their worrying could be self-fulfilling. That is, the bank run scenario where a bunch of cDAI holders try to claim their DAI all at once may happen just because enough cDAI holders are worried about it happening.
Lenders caught in a cDAI bank run can either choose to wait to receive their DAI, or sell their cDAI for DAI, incurring exchange fees and possibly getting a worse price if many other lenders are also selling cDAI for DAI. Should lenders choose to wait it out and hodl cDAI, they will still generate interest in the meantime.
How does Compound address this?
The Compound team is straightforward about this liquidity risk and covers it in their whitepaper:
The protocol does not guarantee liquidity; instead, it relies on the interest rate model to incentivize it. In periods of extreme demand for an asset, the liquidity of the protocol (the tokens available to withdraw or borrow) will decline; when this occur, interest rates rise, incentivizing supply, and disincentivizing borrowing.
Compound determines the interest rate for borrowers for each cToken based on a cToken-specific “interest rate contract”. This contract currently implements the interest rate model for cDAI. The formula is:
Borrower Annual Interest = Base Rate + (Multiplier * Utilization Rate)
For cDAI, the Base Rate = 5% and the Multiplier = 15% (the values are hardcoded into the contract). At a 100% utilization rate the interest paid by borrowers would be 20%. This means that when DAI is maximally utilized, the borrowers are only incentivized to repay their loan from the 20% interest rate — if they believe that ETH (which is used as collateral for the loan) will rise more than 20% over the course of the year, they have no incentive to repay the loan. That could leave a lot of cDAI holders…holding cDAI for a long time.
The only tool that Compound has at their disposal to address this is to use the centralized administrator to upgrade their interest rate model, which is exactly what they did 6 weeks ago when the utilization rate increased to ~99% (same time as the quoted tweet above).
So to summarize, if the utilization rate ever reaches maximum and there is a liquidity crisis and looming bank run, all lenders can do is hope that Compound uses their power to update and increase the interest rate for borrowers to incentivize them to repay their loans and provide liquidity for lenders who want out.
Protocols like Compound perform a delicate dance between centralization and decentralization, trading off between the ability to upgrade quickly and the centralized points of failure that they necessarily introduce in order to do so.
I don’t fault Compound for choosing to bootstrap their product in a centralized way (it has clearly worked or else I wouldn’t be writing this), but I do hope that we hold projects with $10–100M+ in their smart contracts to the highest of standards, especially when it comes to communicating risks to their users and providing warnings.
Basically, we should encourage projects to do the opposite of what Robert Leshner (CEO of Compound) does here:
I’m still undecided about depositing my DAI in Compound. Maybe I’ll start with just 100,000 DAI? What could go wrong… In Compound We Trust!