What do developers need to be aware of to make their contracts and systems safe against flash loans?
What do they have to think about and protect against?
What do developers need to be aware of to make their contracts and systems safe against flash loans?
What do they have to think about and protect against?
As we work on flash loan attack debriefs, we can gather a lot of information about what could have been taken as preventative measures. It's important to understand how these flash loans work before diving into root causes and analytics.
Flash loans are loans given out without collateral and returned in the same transaction that they were lent out. This is possible in the world of smart contracts, because the code can verify whether or not it will be able to return the loan at the end of the block. If yes, it's able to lend as much as the user needs. This is where the attack vectors come in.
We can draw from a study done by academics at the Imperial College London that looks at two notorious attacks in particular. Their main finding is as follows:
This seems to be the #1 cause of attacks at the moment, by far. What is important to note, is that decentralized exchanges are not decentralized oracles. Using Uniswap, Sushiswap, or Curve to get pricing information to execute trades is pulling data from protocols whose price depends solely on liquidity. Looking at the infamous ground zero bZx attack that sparked this wave of attacks, we can see exactly what happens. These flash loans are used to crash and manipulate the price of these decentralized exchanges, which most projects deemed safe to use. The issue here relies in the fact that these protocols prices depend entirely on liquidity. Here's basically how it works:
What auditors and software engineers need to do, is make sure they don't get pricing or data that rely on DEXes. Uniswap is a decentralized exchange, NOT a decentralized pricing oracle, they are each a centralized data point, and using them as such is dangerous to a point where in the last 2 months, ~5 protocols have been hacked for over $30M combined.
This all being said, to prevent these attacks from happening, you need to make sure that when you're getting pricing information, or any data at all, it needs to come from decentralized oracles and get the data from decentralized Chainlink Price Feeds, if it's price data. For any other data, you need to get your data from a decentralized network of Chainlink Oracles. And as an engineer, anyone can customize their oracle network to make it as wide or narrow as they like.
This all being said, at this point, there is enough information out there that if a protocol gets hacked and that protocol paid an auditor, that auditor needs to be held accountable as well, as missing centralized price oracles in audit reviews is going to make this keep happening. Many projects who have been hacked have integrated Chainlink price feeds as there backbone for data reliability and have since stabilized.
To quote:
A reentrancy attack can occur when you create a function that makes an external call to another untrusted contract before it resolves any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the effects were resolved.
Luckily, we know that fallback functions have been patched to use only 2300 gas, so this can be helpful here. Consensys recommends using call
instead of transfer
or send
to protect yourself against these.
I think this is a mistake, I think transfer is actually safer, and just as long as you do all work before you make an external call, ie take something like:
func withdraw() public {
token.transfer(to_address, amount);
balance = 0;
}
and change it to:
func withdraw() public {
balance = 0;
token.transfer(to_address, amount);
}
It should be mentioned that there are some "hacks" that will also fix this issue. Making all your important transactions happen over the course of 2 or more blocks can be a hacky way to get around some of this. Also you will always want to use safeMath
when working with massive integers, and need to make sure your overflows don't break your protocol. Note: I am Alpha Chain CEO & Chainlink DevRel
Flash loans violate two common intuitive assumptions.
Problems can arise when for example:
Problems can arise when for example:
Generally speaking, if your system gives the users different terms under different scenarios, then you need to remember that a user can issue a flash-load attack on it.
For example, if your pool gives the users a better conversion-rate when the pool is deeper (higher reserve balances), then a user can attack it by executing atomically:
This sort of attack can gradually (or even abruptly) drain out your pool.
UPDATE:
The example above is feasible only if the user is guaranteed to receive in the last step the same liquidity (reserve amounts) invested in the first step, regardless of the state (reserve balances) of the pool.
This restriction is not guaranteed in most pools currently deployed, making the illustrated flash-loan attack useless under these circumstances.