The myth of the ERC-20 token “standard”

Michael Feng
Hummingbot Blog
Published in
9 min readJul 12, 2018

People tend to think of ERC-20 tokens as inter-operable. Per Wikipedia:

ERC-20 defines a common list of rules for Ethereum tokens to follow within the larger Ethereum ecosystem, allowing developers to accurately predict interaction between tokens.

To be fair, we held a similar view, until we audited the top 100 ERC-20 Ethereum tokens from a business logic perspective. We felt this was necessary even though most tokens have undergone security audits, since security audits may not uncover business outcomes which affect token holders.

We had to do this audit for because we’re launching the Basket Protocol, an open-source protocol that allows for the creation of non-custodial asset management products. Since our protocol relies on the fact that smart contracts are first-class citizens in Ethereum that can own and transfer ERC-20 tokens, it’s critical to understand token interactions in happy paths and edge cases alike.

It turns out that in order to “accurately predict interactions between tokens,” you actually need to analyze and understand the token contracts themselves. Since ERC-20 is a living standard while smart contracts are immutable, some variations in the token contracts is to be expected. However, we were surprised to discover how significant some of the actual differences were. For example, in certain cases, the control that project developers still hold over token transfers and balances is both disconcerting and at odds with the decentralized ethos of Ethereum.

After completing our audit, we developed a more nuanced understanding of the ERC-20 standard, as it has actually been implemented in the wild. To foster a conversation about what the ERC-20 standard truly represents, we highlight some of these differences in this post.

Note that we are not professional smart contract auditors, so this should be not be construed as a formal audit nor investment advice. Also, we used the smart contract code where available at the respective token address in Etherscan. If you believe any of our conclusions are mistaken, please let us know.

TL;DR

If you want to to skip ahead, here are the different classes of ERC-20 tokens we found:

What exactly is ERC-20?

While ERC-20 may sounds like some type of official decree, it’s in fact just the 20th issue in the Ethereum Requests for Comments (ERC) Github repo. Since there was a clear need for a general standard for fungible tokens, the Ethereum community has embraced it while proposing improvements such as ERC-223 and additional standards such as ERC-721 for non-fungible tokens.

The ERC-20 standard defines the interfaces for a few common methods: i.e. totalSupply, balanceOf, transfer, transferFrom, and approve. These methods allow Ethereum smart contracts to issue fungible tokens and token holders to transfer tokens to one another.

It’s important to note that these standards define the method interface (function names, parameters, and outputs) and describe the purpose of each method. However, they leave the implementation specifics up to each project.

We discovered that only a few projects implement the basic ERC-20 methods alone. Rather, most projects introduce additional conditions that affect how their ERC-20 methods behave. While in many cases, these conditions are by design, but we believe it’s important to understand and classify them.

Plain Vanilla ERC-20

Let’s first raise a glass to the projects who implement the ERC-20 methods without any conditions. They include but are not limited to:

  • RChain (RHOC)
  • 0x Project (ZRX)
  • Populous (PPT)
  • IOSToken (IOST)
  • HuobiToken (HT)
  • Loom Network (LOOM)
  • Polymath (POLY)
  • PowerLedger (POWR)
  • SALT Lending (SALT)
  • Gnosis (GNO)
  • Santiment (SAN)
  • Metal (MTL)

Not ERC-20 (by design or otherwise)

Some of the top 100 Ethereum tokens never purported to be ERC-20 tokens.

  • Golem (GNT): One of the first Ethereum tokens, Golem did their ICO before the advent of the ERC-20 standard and understandably does not adhere to it. Unlike other early tokens such as Augur (REP), Golem has chosen not to migrate their token to ERC-20 and has been upfront about it:

Others claim to be ERC-20 but don’t implement the full spec:

  • AllSportsCoin (SOC): Endorsed by Eden Hazard, this token sports a market capitalization of $42 million currently, perhaps buoyed by Belgium’s success thus far in the 2018 World Cup. Although the token contract inherits from a contract named TokenERC20, this contract does not implement the standard transferFrom and approve functions. This means that storing SOC in a multi-sig wallet effectively burns it. Caveat emptor.

Bonus Features

These tokens have additional features that don’t impact transfers but may change the value calculus for certain market participants:

  • VeChain (VEN): Any transfer of VEN also triggers a one-time “bonus” Transfer event to either the sender or the receiver if these parties have an balance in the accounts.rawTokens ledger. This appears to be a bonus paid to early VEN adopters, but we highlight this feature in case VEN holders want to check if they are eligible for this bonus:
// Claim bonus by raw tokens
function claimBonus(address _owner) internal{
require(isSealed());
if (accounts[_owner].rawTokens != 0) {
uint256 realBalance = balanceOf(_owner);
uint256 bonus = realBalance
.sub(accounts[_owner].balance)
.sub(accounts[_owner].rawTokens);
accounts[_owner].balance = realBalance.toUINT112();
accounts[_owner].rawTokens = 0;
if(bonus > 0){
Transfer(this, _owner, bonus);
}
}
}
// Transfer the balance from owner's account to another account
function transfer(address _to, uint256 _amount) returns (bool success) {
require(isSealed());
// implicitly claim bonus for both sender and receiver
claimBonus(msg.sender);
claimBonus(_to);
...
}
  • Raiden (RDN): In addition to ERC-20, Raiden also implements the ERC-223 specification, which among other improvements, tries to prevent accidental token loss if it is sent to an unaware contract. However, this requires the receiving contract to also implement receiver logic.
  • Dentacoin (DCN): If you transfer DCN back to the Dentacoin smart contract but don’t have enough ETH in your Ethereum wallet to pay for gas, the smart contract will deduct some from the DCN value:
/* Transfer function extended by check of eth balances and pay transaction costs with DCN if not enough eth */function transfer(address _to, uint256 _value) returns (bool success) {// Prevents drain and spam
if (_value < DCNForGas) throw;
// Trade Dentacoins against eth by sending to the token contract
if (msg.sender != owner && _to == DentacoinAddress && directTradeAllowed) {
sellDentacoinsAgainstEther(_value);
return true;
}
...
}

Pausable / Freezable / Lockable

The majority of ERC-20 tokens allow project owners to suspend token transfers. Top tokens with this feature include:

  • Binance Coin (BNB)
  • OmiseGo (OMG)
  • TRON (TRX)
  • Zilliqa (ZIL)

As this example in the OpenZeppelin library shows, the transfer function throws if the contracted is the in Paused state.

contract PausableToken is StandardToken, Pausable {   function transfer(
address _to,
uint256 _value
)
public
whenNotPaused
returns (bool)
{
return super.transfer(_to, _value);
}
...

There are a number of reasons why this may be necessary: Tokens conducting a mainnet swap like EOS and TRON may need to take a snapshot of token ownership. In addition, tokens that discover a vulnerability in their code, such as Augur in October 2017, may need to suspend transfers to mitigate damage until token holders can be migrated to a new token.

Given the pervasiveness of this condition, we believe any smart contracts that hold tokens such as multi-sig wallets and escrow contracts should account for this possibility and notify users of any announced pauses promptly.

Controllable

A potentially more problematic condition is Controllable, which grants another smart contract the ability to enact transferFrom without a prior approval:

function transferFrom(address _from, address _to, uint256 _amount
) returns (bool success) {
// The controller of this contract can move tokens around at will, this is important to recognize! Confirm that you trust the controller of this contract, which in most situations should be
another open source smart contract or 0x0

if (msg.sender != controller) {
if (!transfersEnabled) throw;
// The standard ERC 20 transferFrom functionality
if (allowed[_from][msg.sender] < _amount) throw;
allowed[_from][msg.sender] -= _amount;
}

return doTransfer(_from, _to, _amount);
}

Unlike Pausable contracts in which the owner can only change a single binary setting that affects every token holder, Controllable contracts allow a controller to change token ownership at the individual level. Thus, a malicious controller would be able to steal tokens from any token holder.

Control Released

Luckily, most projects that implemented this mechanism did so in order to retain control during the token sale process and subsequently released it.

For example, Aragon (ANT) described this process in their token sale technical overview:

When the ANT sale is finalized, controller power will be released to a placeholder contract until the Aragon Network is deployed, when it will forward controller power to the Network. This contract cannot take any action towards ANT, it just allows transactions.

The smart contract for the ANT controller seems to bear this out, since the changeController function only allows the sale address to change the controller to a burned address with no further functionality:

contract ANPlaceholder is Controller {    function ANPlaceholder(
address _sale,
address _ant
) {
sale = _sale;
token = ANT(_ant);
}
function changeController(address network) public {
if (msg.sender != sale) throw;
token.changeController(network);
suicide(network);
}
...
}

Control Retained

However, certain projects did not follow this model and instead retained authority over their controller smart contracts after the token sale.

For example, compare the controller smart contract for Status (SNT) versus that for Aragon above:

contract SNTPlaceHolder is TokenController, Owned {    function SNTPlaceHolder(
address _owner,
address _snt,
address _contribution,
address _sgtExchanger
) {
owner = _owner;
snt = MiniMeToken(_snt);
contribution = StatusContribution(_contribution);
sgtExchanger = _sgtExchanger;
} /// @notice The owner of this contract can change the controller of the SNT token. Please, be sure that the owner is a trusted agent or 0x0 address.
function changeController(
address _newController
) public onlyOwner {
snt.changeController(_newController);
ControllerChanged(_newController);
}

Unlike Aragon, the Status controller contract is Owned by another address, which appears to the Status project’s multi-sig wallet. Since the Owner of the Controller contract can arbitrarily change its address, and the Controller contract can performtransferFrom transactions in the Status token contract, this means that the Status project team has unlimited authority over individual Status holder token balances.

FunFair (FUN), anotherControllable contract, has a similar mechanism in which the Owner can upgrade Controller and Ledger contracts, enabling it to change not only token balances but also the entire token-economic model. This fact was discussed in the security audit published on the FunFair website:

The FunFair Token contracts allow upgrading of the Controller and the Ledger. The desire to do this is almost certainly an acknowledgement that the FUN mechanics — and real implementation details about them — aren’t sorted yet.

Rather than inconvenience users with a new FUN token address, these tokens allow the logic to be switched out at a later date in a single step. This is cool, and I like the idea.

Similarly the ledger is switchable; also a good idea.

However, this very upgradability gives rise to the chance for bad actions by the owner of the contracts. The contracts have a “finalization” step which can be used to lock the logic layers down, and the FunFair whitepaper tells readers to expect that the logic will be locked eventually.

Until that lock happens, the contracts are simply controlled by the owner. This seems like a sensible trade, and in fact, I expect some high profile token contract upgrades in the next six months as existing projects realize they cannot support all necessary business plans with their old tokens.

However, be warned. If you do not believe the FunFair team is trustworthy, you cannot (yet) trust the Token contracts.

The security audit was performed when FunFair conducted their ICO in June 2017. More than one year later, the contract is not yetfinalized and the FunFair project still has full discretion over token balances and economics.

Conclusion

Our intent is not to create FUD for projects like Status, FunFair, and others who still retain authority over their ERC-20 tokens. Despite the decentralization ethos to which everyone in blockchain aspires, we recognize the need for centralization in early projects.

However, we call upon any project with centralized authority over their tokens (whether they use Pausable, Controllable, or other paradigms) to be transparent to token holders about:

  • Specific circumstances when they will use this authority and when they will not, and;
  • A definitive plan to relinquish this authority.

Special thanks to Yvonne Zhang from the Team CoinAlpha who performed all the audits. Thanks to @carlol, Li Jiang, Zi Wang, and Stephen Tse for providing feedback and comments. For more information, check out the Awesome Buggy ERC-20 Tokens open-source repo maintained by our friends from SECBIT.

--

--