At the end of 2019, I published an article titled Taking undercollateralized loans for fun and for profit. In it, I described economic attacks on Ethereum DApps that relied on accurate price data for one or more tokens. It is now the end of 2020, and unfortunately, many projects have made very similar mistakes since then, the most recent example being the Harvest Finance hack, which caused the protocol users to collectively lose $24 million. While developers are familiar with vulnerabilities such as reentrancy, price oracle manipulation is clearly not something that people think about often. On the contrary, vulnerabilities based on reentrancy have declined over the years, while vulnerabilities based on price oracle manipulation are now on the rise. Therefore, I decided it was time for someone to publish a definitive resource on price oracle manipulation. This post is divided into three parts. For those who are not familiar with the subject, there is an introduction to oracles and oracle manipulation. Those who want to test their knowledge can jump to the case studies, where we review past oracle-related vulnerabilities and exploits. Finally, we summarize some techniques that developers can use to protect their projects from oracle manipulation. Oracle Manipulation in Real Life It's Wednesday, December 1, 2015. Your name is David Spago, and you're at the Peking Duke concert in Melbourne, Australia. You want to meet the band in person, but there are two security guards standing between you and the backstage passage, and there's no way they're going to let some normal person just walk in. You wonder how the security guards will react if you act like a normal person. The band's families will definitely be allowed backstage, so all you have to do is convince the security guards that you are their family. You think about it for a while and come up with a plan that can only be described as genius or crazy. After quickly arranging everything, you confidently walked up to the security guard. You introduced yourself as David Spago, a family member of Peking Duk. When the security guard asked you to provide evidence, you showed them irrefutable evidence - Wikipedia. Security waves you over and asks you to wait. Five minutes pass and you wonder if you should flee before law enforcement shows up. As you prepare to leave, Reuben Styles walks up and introduces himself. You walk with him to a green room where the band is impressed with your ingenuity and you end up having a few beers together. Later, they share what happened on their Facebook page. What is a price oracle? A price oracle is basically anything you can ask for price information. When Pam asks Dwight for the cash value of the Schrute Buck, Dwight is acting like a price oracle.
On Ethereum, everything is a smart contract, and so are price oracles. Therefore, it is more useful to understand how price oracles obtain price information. One way is that you can simply fetch existing off-chain price data from a price API or exchange and bring it on-chain. Another way is that you can calculate the instant price by consulting an on-chain decentralized exchange. Both options have their own advantages and disadvantages. Off-chain data is generally slower to react to fluctuations, which can be a good or bad feature depending on what you are using it for. However, it usually requires a small number of privileged users to push data on-chain, so you have to trust that they will not go bad and cannot be coerced into pushing bad updates. On-chain data does not require any privileged access and is always up to date, but this means it can be easily manipulated by attackers, which can lead to disastrous consequences. What could possibly go wrong?
Let’s look at a few cases where a poorly integrated price oracle led to significant financial losses for DeFi projects.
Synthetix sKRW Oracle Failure Synthetix is a derivatives platform that allows users to gain exposure to assets such as other currencies. To achieve this, Synthetix (at the time) relied on a custom off-chain price feed implementation, where an aggregate price calculated from a set of secret price feeds was published on-chain at fixed intervals. These prices then allowed users to trade long or short against supported assets. On June 25, 2019, one of the price feeds that Synthetix relied on incorrectly reported a price for the Korean won that was 1,000 times higher than the true exchange rate. Due to other errors elsewhere in the price oracle system, this price was accepted by the system and posted on-chain, where a trading bot quickly bought and sold on the sKRW market. In total, the bot was theoretically capable of making over $1 billion in profits, although the Synthetix team was able to negotiate with the trader to return the funds in exchange for a bug bounty. Synthetix correctly implemented the oracle contract and pulled prices from multiple sources to prevent traders from predicting price changes before they were published on-chain. However, an isolated case of a failure in an upstream price source led to a devastating attack. This illustrates the risk of price oracles that use off-chain data: you don’t know how the price was calculated, so your system must be carefully designed so that all potential failure modes are properly handled. Mortgage As mentioned earlier, I published an article in September 2019 outlining the risks associated with using price oracles that rely on on-chain data. While I highly recommend reading the original post, it is quite long and contains a lot of technical detail that can be difficult to digest. Therefore, I will provide a simplified explanation here. Imagine you want to bring decentralized lending to the blockchain. Allow users to deposit assets as collateral and borrow other assets up to the value of the assets they deposited. Let's say a user wants to borrow USD using ETH as collateral, and the current price of ETH is $400, with a collateral ratio of 150%. If a user deposits 375 ETH, that’s equivalent to $150,000 in collateral. For every $1.5 of collateral, they can borrow $1, so they can borrow up to $100,000 from the system. But of course, on a blockchain, it’s not as simple as simply announcing that 1 ETH is worth $400, because a malicious user could simply announce that 1 ETH is worth $1,000 and take all the money out of the system. Therefore, it’s tempting for developers to just get the most recent price that an oracle reads, like the current spot price on Uniswap, Kyber, or another decentralized exchange. At first glance, this seems like the right thing to do. After all, whenever you want to buy or sell ETH, the price on Uniswap is always roughly correct, as any deviations are quickly corrected by arbitrageurs. However, it turns out that during the course of a trade, the spot price on a decentralized exchange can be significantly wrong, as shown in the example below. Consider how Uniswap’s reserves work. Prices are calculated based on the amount of assets held in the reserve, but as users trade between ETH and USD, the assets held in the reserve change. What if a malicious user trades before and after taking a loan from your platform? Before the user took out the loan, they purchased 5,000 ETH with $2,000,000. The Uniswap exchange now calculates the price to be 1 ETH = $1,733.33. Now, their 375 ETH can be used as collateral for $433,333.33 worth of assets that they borrowed. Finally, they exchange 5,000 ETH back for their original $2,000,000, thereby resetting the price. The end result is that your lending platform simply let the user borrow an extra $333,333.33 without putting up any collateral. This case study illustrates the most common mistake when using a decentralized exchange as a price oracle - the attacker has almost complete control over the price during the transaction, and trying to read this price accurately is like reading the weight on a scale before it completes settlement. You will most likely get the wrong number, which, depending on the circumstances, could cost you a lot of money. Synthetix MKR Manipulation In December 2019, Synthetix was attacked again due to price oracle manipulation. This time, it was notable that it crossed the barrier between on-chain price data and off-chain price data. Reddit user u/MusaTheRedGuard observed that an attacker made some very suspicious transactions for sMKR and iMKR (inverse MKR). The attacker first bought a long position in MKR by buying sMKR, and then bought a large amount of MKR from the Uniswap ETH/MKR trading pair. After waiting for a while, the attacker sold their sMKR for iMKR and sold their MKR back to Uniswap. Then, they repeated the process. Behind the scenes, the attacker’s transactions through Uniswap allowed them to change the price of MKR on Synthetix at will. This is likely because the off-chain price feed that Synthetix relies on is actually dependent on the on-chain price of MKR, and there is not enough liquidity for arbitrageurs to reset the market back to the optimal state. This incident illustrates that even if you think you are using off-chain price data, you may actually still be using on-chain price data, and you may still be exposed to the complexity of using this data. bZx hack In February 2020, bZx was hacked twice within a few days, losing about $1 million. You can find an excellent technical analysis of both hacks written by palkeo here, but we will only look at the second hack. In the second hack, the attacker first bought almost all of the sUSD on Kyber with ETH. The attacker then bought a second batch of sUSD from Synthetix themselves and deposited it on bZx. The attacker used the sUSD as collateral to borrow the maximum amount of ETH they were allowed. They then sold the sUSD back to Kyber. If you’ve been paying attention, you’ll recognize that this is essentially the same collateralized loan attack, but using different collateral and a different decentralized exchange. yVault Bug On July 25, 2020, I reported a bug to yEarn regarding their new yVault contract rollout. I’ll briefly summarize it below. The yVault system allows users to deposit tokens and earn a yield on them without having to manage them themselves. Internally, the vault tracks the total issuance of yVault tokens and the total amount of base tokens deposited. The value of a single yVault token is given by the ratio of minted tokens to deposited tokens. Any yield earned by the vault is distributed among all issued yVault tokens (and therefore, all yVault token holders). The first yVault allows users to earn a yield on USDC by providing liquidity to the Balancer MUSD/USDC pool. When users provide liquidity to the Balancer pool, they receive BPT in return, which can be redeemed for a portion of the pool. Therefore, the yVault calculates the value of its holdings based on the amount of MUSD/USDC that can be redeemed with its BPT. This seems like the right way to do it, but unfortunately the same principle as given before applies - the state of the Balancer pool is not stable during a transaction and cannot be trusted. In this case, due to the price curve chosen by Balancer, when a user swaps from USDC to MUSD, they will not get a 1:1 exchange rate, but will in fact have some MUSD left in the pool. This means that the value of BPT can be temporarily inflated, which allows an attacker to manipulate the price at will and subsequently drain the vault. This incident shows that price oracles do not always clearly reflect price data, and developers need to be wary of what kind of data they ingest and consider whether it can be easily manipulated by unauthorized users. Hacks on Yield Farming On October 26, 2020, an unknown user hacked into the liquidity mining pool using a technique you may have guessed by now. You can read the official post-mortem here, but I will summarize it for you again: the attacker executed transactions to devalue the price of USDC in the curve pool, entered the farming pool at a reduced price, restored the price by reversing previous transactions, and exited the farming pool at a higher price. This resulted in a loss of more than $33 million. How can I protect myself? By now, I hope you have learned to recognize the common ground - you are not always safe using price oracles, and if you don't follow proper precautions, an attacker could potentially hack your protocol and send all your money to them. While there is no one-size-fits-all fix, here are some solutions that have worked for other projects in the past. Maybe one of them will work for you too. Find a pool with sufficient liquidity Like jumping into the shallow end of a swimming pool, jumping into an illiquid market is painful and can result in significant expenses that will change your life forever. Before you consider the complexity of the specific price oracle you intend to use, consider whether the token is liquid enough to warrant integration with your platform. A bird in the hand is worth two in the bush It can be fascinating to see a potential exchange rate on Uniswap, but until you actually click on the trade and the tokens are in your wallet, it doesn’t mean that’s the final price. Likewise, the best way to determine the exchange rate between two assets is to swap the assets directly. This method is great because there are no kickbacks and no chances. However, it may not be suitable for protocols such as lending platforms, which require holding the original assets. Decentralized Oracles One way to summarize the problem with oracles that rely on on-chain data is that they are a little too new. In that case, why not introduce a little artificial delay? Write a contract that updates itself with the latest price from a decentralized exchange like Uniswap, but only when a small group of privileged users ask it to. Now even if an attacker can manipulate the price, they can't make your protocol actually use it. This approach is really simple to implement and is a quick fix, but has some drawbacks - in times of link congestion you may not be able to update prices as quickly as you'd like, and you're still vulnerable to sandwich attacks. Also, now your users need to trust that you'll actually keep the prices updated. Delay Defense Manipulating price oracles is a time-sensitive operation, as arbitrageurs are always watching and hoping for an opportunity to optimize any suboptimal markets. If an attacker wants to minimize their risk, they will want to complete the two transactions required to manipulate the price oracle in one transaction, leaving no opportunity for arbitrageurs to jump in the middle. As a protocol developer, you may only need to implement a delay as short as 1 block between users entering and exiting the system, if your system supports it. Of course, this could affect composability, and miner-trader collaboration is on the rise. In the future, bad actors could potentially manipulate price oracles across multiple exchanges, knowing that the miners they work with will ensure that no one can jump in the middle and take a cut of their gains. Time Weighted Average Price (TWAP) Uniswap V2 introduces a TWAP oracle for on-chain developers to use. The specific security guarantees provided by this oracle are described in more detail in the documentation, but in general, for large pools with no on-chain congestion for a long time, the TWAP oracle is very resistant to oracle manipulation attacks. However, due to the nature of its implementation, it may not be responsive enough in moments of high market volatility, and it only works for assets that already have liquid tokens on-chain. M-of-N Price Feed Sometimes people say that if you want something done right, you do it yourself. What if you gathered N trusted friends and asked them to submit what they thought was an appropriate on-chain price, and the best M answers became the current price? Many large projects are using this approach today. Maker runs a set of price feeds operated by trusted entities, Compound created Open Oracle and has reporters like Coinbase, and Chainlink aggregates price data from Chainlink operators and makes it public on-chain. Just remember that if you choose to use one of these solutions, you have now delegated trust to a third party, and your users must do the same. Requiring reporters to manually post updates on-chain also means that price updates may not be completed in time during times of market volatility and on-chain congestion. in conclusion Price oracles are an important, yet often overlooked, component of DeFi security. Using price oracles safely is hard, and there are many ways they can be exploited by both you and your users. In this post, we covered examples of past price oracle manipulation and established that reading price information in the middle of a transaction can be unsafe and could result in catastrophic financial loss. We also discussed some techniques that other projects have used in the past to combat price oracle manipulation. In the end, every situation is unique, and you may find yourself unsure if you are using a price oracle correctly. SamczsunAuthor OliviaTranslation OliviaEdit
|