The latest big thing in Ethereum is Decentralised Finance (DeFi). Central applications of DeFi are lending, staking and trading ERC20 tokens. To use ERC20 tokens in DeFi protocols such as Uniswap, Aave or Yearn you have to grant the dapp permission to spend tokens on your behalf - known as an ERC20 allowance. These allowances are integral to the functioning of DeFi platforms, but can be dangerous if left unchecked.
Note: this post specifically talks about ERC20 allowances, but it also applies to NFTs.
Why are ERC20 allowances necessary?
With Ethereum's native ETH token it is possible to call a smart contract function and send ETH to the contract at the same time. This is done using so-called payable functions. But because ERC20 tokens are smart contracts themselves, there is no way to directly send tokens to a smart contract while also calling one of its functions.
Instead, the ERC20 standard allows smart contracts to transfer tokens on behalf of users - with the
transferFrom() function. To do so, the user needs to allow the smart contract to transfer those tokens on their behalf.
This way, a user can "deposit" tokens into a smart contract, and at the same time, the smart contract can update its state to reflect the deposit. In contrast, if you just send ERC20 tokens to the contract, the contract cannot update its state (e.g. to credit the deposit to your account).
As an example, if you want to "deposit" DAI into Aave to earn interest, you need to first allow the Aave contract to take some DAI from your wallet. Then you call a smart contract function on the Aave contract where you specify how much DAI you want to deposit. The Aave contract then takes exactly that amount from your wallet using the
transferFrom() function and credits you with the same amount of aDAI tokens.
Why are unlimited ERC20 allowances harmful?
When depositing a specific amount (say 100 DAI) into a contract, you can choose to set an allowance of exactly that amount. But instead, many apps instead request an unlimited allowance from the user.
This offers a superior user experience because the user does not need to approve a new allowance every time they want to deposit tokens. By setting up an unlimited allowance, the user just needs to approve it once, and not repeat the process for subsequent deposits.
However, this setup comes with significant drawbacks. As we know, bugs can exist and exploits can happen even in established projects. And by giving these platforms an unlimited allowance, you do not only expose your deposited funds to these risks, but also the tokens that you're holding "safely" in your wallet.
I first talked about this with Paul Berg at Devcon 5, where he gave a presentation about precisely the issues discussed in this article. While developing Sablier, Paul found (and fixed!) a bug in his smart contracts, where not only the deposited DAI ($100) were at risk, but also all DAI in the testers' wallets ($10k)!
Real world risks
For a long time the risk of unlimited allowances was largely theoretical, and Paul's Sablier bug was fixed before the platform went into production. Back then there hadn't been any exploits that took advantage of ERC20 allowances, but it was bound to happen as platforms kept using unlimited allowances.
And over the past years we have seen several such exploits.
In early 2020, Bancor experienced a bug that put users' funds at risk. The function that executes the ERC20
transferFrom() function was accidentally made public (rather than private to the contract), which allowed anyone to execute it and drain the users' wallets. Bancor performed a white-hat hack of the contract to contain the damage and returned the funds back to users.
Another high profile bug exploit was the Furucombo hack in February 2021, where a bug in the Furucombo protocol enabled a hacker to drain the wallets of people who gave an allowance to Furucombo, even if they didn't have any funds deposited into the contract directly.
While bugs can happen even in reputable and legitimate projects, there have also been cases where the project itself was malicious. During 2020's DeFi summer, people were jumping at every new food-themed DeFi fork, which included some outright scams. And even if people tried to limit their risk by only depositing small amounts, funds in their wallets were still at risk because of unlimited ERC20 allowances.
ZenGo reported one such exploit in a project called UniCats. People could deposit their Uniswap (UNI) tokens to farm MEOW tokens (you can't make this up). But to do the deposit, they had to grant an unlimited allowance. When the project inevitably rug-pulled, the scammers not only took the deposited funds, but also all UNI tokens that users had in their wallets.
Another such case happened with a project called Degen Money, which used a slightly less sophisticated (but not less effective) approach. Rather than developing their own smart contract, they created a frontend that made two approval transactions. One to a functioning contract, and one to a completely different address.
Because many people do not specifically check the contract addresses, this allowed the attackers to drain users' wallets.
What about hardware wallets?
In general, hardware wallets (such as the Ledger Nano X) are much safer than mobile or browser-based wallets. The reason for this is that the private keys that control the funds are securely stored on the hardware wallet and never leave the device. So by using a hardware wallet you ensure that no one can steal your private keys.
The problem with ERC20 allowances though, is that no one needs to steal your private keys to take the tokens from your wallet. And because of that, hardware wallets offer no protection whatsoever to the exploits discussed in this article.
It is still good practice to use a hardware wallet because they do protect you against a range of other possible exploits. But you need to be aware that they do not protect against allowance exploits or many other smart contract exploits.
What can dapp developers do?
In his talk at Devcon, Paul mentioned several possible solutions to the unlimited allowance problem, which all have different strengths and drawbacks. The most practical of those solutions is using the approve-spend pattern. In this pattern you only request the user to approve the exact amount that they want to use at that moment, rather than an unlimited amount.
This is a worse user experience since the user needs to send a new approval transaction every time they want to send a transaction, rather than doing one single approval. This also has the added drawback that it costs more in transaction fees, which can be especially troublesome when fees rise like they did last summer.
So a better option is to offer the choice to users where they can either choose to approve only what they need to spend at the time, or they can approve a much larger amount if they intend to do more transactions in the near future. This strategy is already employed by several projects, such as Zapper.fi and Curve.fi.
An alternative solution to mitigate transaction costs is adopting EIP2612 (permit). This standard enables users to sign a message to set their allowance (which is free), rather than having to send a transaction to do so. The developer ecosystem around EIP2612 is small, but it is growing rapidly and projects such as Uniswap are using it for their lending provider tokens.
What can users do?
Since ERC20 allowances are integral to the functioning of many smart contracts, it is not an option to stop approving allowances altogether. But where possible, try to avoid unlimited allowances.
People are more aware of this issue than a year ago, so some dapps offer the option to only approve the amount you're spending at the moment, but most apps still don't. Even then, advanced users can lower their allowance through Metamask's interface.
When you're using a dapp try to consider if you're going to be using this dapp a lot and you trust the project (unlimited allowance), or if you'll use dapp infrequently or you do not trust the project (smaller allowances). In either case it is also a good practice to review your outstanding allowances periodically (say every month), and revoke those that you're not actively using any more.
To inspect and revoke these allowances, I developed a tool called revoke.cash. It gives an overview of an address' token balances and corresponding allowances. Allowances can then easily be revoked or lowered. Similar tools such as approved.zone can also be used for this.
How is revoking different from disconnecting MetaMask?
MetaMask has an option to disconnect any accounts that you previously connected to a website. Some people think that using this disconnect button will protect them from exploits. This is not the case.
Disconnecting MetaMask does not do anything to protect you from allowance exploits - or most other exploits. The only thing that you achieve when disconnecting MetaMask from a website is that that website cannot see your address any more.
Allowances are necessary for the functioning of many decentralised applications, but at the same time unlimited allowances are generally harmful to security. We've seen several exploits in the past two years that take advantage of ERC20 allowances, and the awareness of the issue is much higher today than it was in 2019. There are a few things you can do as a user to mitigate the discussed risks, including periodically reviewing and revoking outstanding allowances.