In the wake of $1M+ of ETH lost in bZx hack #1 and bZx hack #2, decentralized financial security is now in the spotlight. Both of these attacks were crafted through the (clever) use of a relatively new DeFi feature: flash loans.
As we discussed and analyzed these attacks we came up with 4 questions that should be public knowledge regarding our security in this rapidly evolving industry.
tl;dr — no.
Let’s breakdown the attack to a mere two steps for better understanding:
- Short ETH-WBTC on bZx with such a large value that the account becomes instantly undercollateralized due to horrendous slippage
- Profit on this market by performing a counter trade on kyber simultaneously
Note that we cut out the flash loan in the step above. The flash loan here actually isn’t REQUIRED for this attack to work — it just makes it way easier for someone to do it without staking a bunch of their own money. With enough assets, someone could have made this attack even without flash loans.
Step one is where bZx lost funds. Our checks are explicit — it’s not possible to open an undercollateralized position on DDEX.
There’s a few takeaways here:
- Learn from your mistakes. Principles of good engineering tell us that we should ask “where else could this happen?” and “what are the root causes which lead to this mistake? How can they be avoided in the future through habit change?”
This attack on bZx happened because the contract failed to execute a final check to make sure that the math added up in everyone’s favor. How was it that this code made it so far without anyone noticing?
This leads us to takeaway #2
- Auditors are not magicians. Global invariants should be made as obvious as possible.
Code quality cannot be undervalued in this realm. We strive to make our code explicit and obvious — this makes the auditors job easier, and reduces the chance for strange attack variants taking advantage of overly complex code.
- Automatic Market Makers (AMMs) can be “spring loaded” to temporarily store value (in the form of a lucrative trade)
While AMMs are super cool, they add a huge level of complexity to security as they effectively give attackers a spring to load/deload for realizing profit. DDEX doesn’t use AMMs at all.
tl;dr — no.
Hack #2 was pretty straightforward price oracle manipulation, which once again resulted in a massively undercollateralized loan defaulting on bZx.
There are several reasons that DDEX is not vulnerable to this type of oracle attack. These are highlighted as we discuss our major takeaways from this below:
- Oracles that completely depend on other DeFi projects are dangerous
Oracles that rely on markets without an enormous amount of liquidity form easy attack points, as seen in hack #2.
DDEX utilizes an onchain pool with a hybrid exchange. This allows us to use DeFi + our own books + centralized exchanges. For example our USDC references Coinbase prices, with safety checks, outlier detection, and expiration time built into our onchain proxy. Other well engineered projects like dydx do similar things as well.
- A root cause of using weak oracles is that some tokens (such as sUSD) simply do not have enough global liquidity
While it my be attractive to list exotic tokens for unique offerings, they should be limited to spot exchanges only. Adding long tail tokens to a lending pool is extremely dangerous. DDEX for the foreseeable future will limit our margin assets to BTC, ETH, and stablecoins.
- Listing long tail tokens affects all tokens
Some would say “who cares if they list XXX, only people who touch XXX are affected and they are responsible for their own behavoirs.” However, this is ONLY true in the case of a spot exchange. This does not hold true for lending pools.
When the Poloniex crash happened, CLAM crashing caused users who only touched BTC to lose money.
The bZx attack on sUSD caused losses to the ETH pool.
To summarize: keep oracles secure by using the largest liquidity sources available, not just on DeFi, and don’t list random tokens.
Flash loan attacks are fascinating and powerful, but they have a weakness: they have to do everything within a single block. DDEX uses this constraint to protect itself:
We do not allow lending and trading to be performed in a single attack
On DDEX, the act of lending and/or borrowing must be a user submitted transaction. Trading is a relayer (DDEX) submitted transaction. By design, you cannot compose these into a single block.
- Inter-contract composition performed within a single block is dangerous. By constraining the amount of composition to those that actually generate positive value for users, we limit the attack surface. Don’t just enable stuff just to be cool.
- Simplify the thought of “if the attacker had large amounts of tokens then they can do X” to just “they can do X”
Assume that someone out there has the capital for it.
As we watched these attacks unfold, we thought about how we would handle this type of situation ourselves if we found ourselves in bZx’s unfortunate shoes. There are a few main takeaways for things that we definitely would do, and things that we definitely wouldn’t do.
- Don’t live patch through an upgrade system: shutdown the contract and release a completely new version
0x did it the right way — at a high cost of downtime and forced upgrades, even when they had a proxy system in place similar to bZx.
- This isn’t right or wrong, but bZx chose to leave the bad loans on the books. We would not consider this option.
We would reimburse and liquidate the position immediately. If we didn’t have the money, we would still liquidate and socialize the loss to lenders — although this would be bad PR compared to just claiming “no losses”, it’s still the right thing to do from a long term perspective.
- Don’t live upgrade in general — upgradability should be limited to relatively harmless operations
Things like interest rate parameters and small tweaks to platform performance are fine. However, changes to the underlying logic is extremely dangerous — the pain of re-launching a new contract is a good thing.
The motto of “move fast and break things” should be done on the UI, not the contract.
- Publicly present the problem in its simplest form — not its most complex. Don’t use buzzwords at all.
In the spirit of observability, it makes sense to break this down to be easily digestible for your users. Even if it doesn’t feel good from a PR perspective, it’s the right thing to do in the long term.
Thanks for reading! We hope this helps bridge the understanding of security in this ever-changing and complex realm. Check us out, drop us a line if you ever have any questions, or come chat on our discord: https://discord.gg/g6C6jfB