Token Interaction Checklist
Tokens have long been a part of the history of blockchain and cryptocurrencies. As far back as the early days of Bitcoin, there were plans of creating ‘colored coins’ to extend functionality to new use cases. Projects such as Mastercoin, later rebranded as Omni, popped up to fulfill this vision, eventually inspiring Vitalik Buterin to produce the Ethereum whitepaper.
Today’s ecosystem is a highly composable, vast expanse of tokens with a practically endless list of use cases. Although several token standards have been constructed, the very first token standard, ERC-20, remains the most used as a result of the high degree of confidence in its security and simplicity. However, with its unparalleled level of security, the standard has its limitations, thereby inviting the creation of new token standards to increase the range of ever-growing use cases.
In order to fix ERC-20 limitations, some other token standards were created focused on making small, fundamental changes to the standard. For example, ERC-621 was created to allow for a variable totalSupply such that tokens can be minted and/or burned. Another significant early token standard was ERC-721 which introduced non-fungible tokens (NFTs). Though these standards were different from ERC-20, they all tried to be backward compatible with ERC20, and they didn’t actually reinvent the wheel, instead they simply added features. The main reason for backward compatibility was the fact that many DApps (DeX, etc) already had ERC20 support in place, which required the standard token interface to interact with those DApps, especially the allowance approval process.
Seeing as ERC-20 is so limited, ERC-777 was created as a sort of ERC-20 2.0. It was crafted to be backwards compatible with ERC-20 while improving user experience and simplifying smart contract development. Among others, the standard included changes to support functionality for simpler transfers and authorization. However, ERC-777 added extra complexity to the token implementation and raised issues with some of the assumptions DApps had regarding how a token should function.
The Security Implications
Introducing features to a highly composable, base-layer contract is far from a trivial matter. When there are hundreds, if not thousands, of different uses for tokens, one small change can introduce many vulnerabilities. Countless exploits have occurred as a result of developers deviating, even only slightly, from the ERC-20 token standard assumptions. Let’s take a look at some past exploits relating to non-standard token contracts.
Some of these issues resulted from the simplest deviation from the standard, like what and how a function returns a value. As an example, _transferr_ing tokens in some ERC20 implementations return True on a successful transfer, while some others don’t. This can easily trick a DApp that is expecting a True return on a successful transfer to assume the transfer has failed. Additionally, a fundamental issue was found in the way allowance approval was implemented in the original ERC20 design, in which a malicious approved attacker can try to spend more than he is allowed on any allowance changes.
In June 2020, two balancer pools were drained of funds as a result of the contained tokens’ deflationary nature. The affected pools contained STA and STONK respectively, which burn a portion of tokens with every transfer. This feature allowed the attacker to continuously trade the tokens until the supply of the deflationary tokens was reduced enough that the pricing formula had set an enormously high price per token, allowing the attacker to cheaply trade the token for other assets in the pools. This was an issue mainly as Balancer pool implemented an internal accounting for the user balances, assuming that the token behaved as it should; however, deflationary tokens were never thought of in that process. Accounting discrepancy errors such as this are quite common, and awfully tedious to prevent in tokens with a non-fixed supply, particularly if the token balance changes within contracts that it’s deposited to.
Earlier in 2020, a vulnerability was exploited on an ERC-777 Uniswap pool. This attack leveraged the ability of ERC-777 tokens to execute arbitrary code to perform a reentrancy attack. As a result, the liquidity pool was drained of about $300k.
It’s no coincidence that both of these exploits occurred in liquidity pools. This is because the level of smart contract composability is generally correlated with the amount of possible attack vectors and the fact that many liquidity pools accept user-inputted tokens. Outside of liquidity pools that may allow for reentrancy and/or accounting exploits, these token standards are relatively safe to use, as long as the DApp developer has thought about different behaviours in the token designs when interacting with their DApp.
This correlation of complexity and vulnerability is not limited to non-standard tokens. In fact, ERC-20 has several composability gotchas too, even when entirely standard. For example, it’s possible to front-run changes to ERC-20’s approve() function to withdraw more tokens than a user intends to allow. Additionally, external calls can result in a DoS with an unexpected revert, or if the return value is unchecked, execution may resume even if an exception is thrown.
As a result of all these gotchas that arise with the use of different token standards, we’ve created a checklist for developers and security engineers to make use of when working with contracts that interact with many different tokens, especially if they want to support user-inputted tokens. We highly recommend that you understand and check for each relevant vulnerability before working with a specific token standard.
This table will be updated often, check back to update your internal security and development requirements.
Although tokens are integral to this ecosystem, they are imperfect, and as such, engineers should carefully consider their flaws and features when working with them. As with all smart contract development, your work can carry significant amounts of real world value, so it’s crucial that you proceed carefully. We believe DApp developers need to count in all token behaviours, and as the result code their DApp in a way that cannot be exploited with different token implementations, at least for the well known token behaviours.
Acknowledgement. Special thanks goes to Kaden Zipfel for helping writing the initial draft of this article. Also thanks to Dan Robinson, John Mardlin for helping reviewing the checklist.
Thinking about smart contract security? We can provide training, ongoing advice, and smart contract auditing. Contact us.