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.

Attempted Solutions

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.

Token Checklist Table

Last updated: 1 April 2021. Check this gist for the most up to date version of the following table which is updated often by Consensys. The rest of this section's text was copied from the original blog post published November 2020.

Token

Feature

Known Vulnerabilities

Resources

Examples

ERC20

Allowance

Double withdrawal (front-running)

decimals()

The decimals can be more than 18

YamV2 has 24 decimals

Not accounting for the tokens that try to prevent multiple withdrawal attack

Unprotected ‍‍‍‍‍‍‍transferFrom()

External Calls

Unchecked Call Return Value

DoS with unexpected revert

Transfers

Might return False instead of Revert

Missing return value

BalanceOf()

Internal Accounting discrepancy with the Actual Balance

aToken

Blacklistable

Blacklisted addresses cannot receive or send tokens

USDC (FiatToken)

Mintable / Burnable

TotalSupply can change by trusted actors

Pausable

All functionalities can be paused by trusted actors

Deflationary Tokens

Take fees from transfers

Internal Accounting discrepancy with the Actual Balance

STA, STONK

Inflationary Tokens

AirDrop interest to token holders

Internal Accounting discrepancy with the Actual Balance

Compound

ERC1400

Permissioned Addresses

Can block transfers from/to specific addresses

Polymath tokens

Forced Transfers

Trusted actors have the ability to transfer funds however they choose

ERC777

Callbacks / Hooks

Reentrancy

pTokens

Receiver mining GasToken

Receiver blocks the transfer

In case of iterative push transfer can block all transfers

ERC1644

Forced Transfers

Controller has the ability to steal funds

ERC621

Control of totalSupply

totalSupply can be changed by trusted actors

ERC884

Cancel and Reissue

Token implementers have the ability to cancel an address and move its tokens to a new address

Whitelisting

Tokens can only be sent to whitelisted addresses

Conclusion

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.

Last updated