We’ll continue our instructive series with advice for ERC20 integration today. This post will look at the guidance you’ll need to integrate fungible tokens into your project, as well as audit and even design such integrations using ERC20 as an example!
ERC20 is not a technology, software, or piece of code. It is a technical specification. If a token implements the spec, it is an ERC20 token. The ERC20 protocol standard contains basic functions that any useful token should implement to enable trading. — James Seibel
You will also find a list of tools and research for self-study, and we strongly recommend that you read it separately for better understanding! However, there will also be advice for other integrations. I promise it will be very interesting, informative, and concise all at the same time — we know you don’t have a lot of spare time, and we respect that.
I will also share our own observations and give some advice in the next article in our series, since our team has been working since 2016, we have accumulated quite a few of them!
But, first and foremost, I would like to express our sincere gratitude to the authors of all the resource materials, and of course our pessimistic.io auditors who have helped me so much by revealing needed information and lifting the curtain of secrecy.
We believe there is no one who doubts that the basis of any secure integration is a special approach to writing code. Consequently, this article will be focused only on those aspects that can be really useful for making your code safe and secure.
Therefore, below you will see not a typical article, but a Systematization of Knowledge — SoK, in which I will rely on Authors that I myself trust in this matter, and of course our pessimistic.io auditors! Let’s get started!
Since our team has been working since 2016, we have accumulated quite a few observations (check out I — VII), which we will share below along with several security advices.
We understand and respect your limited time, therefore we created a specific cheat sheet with nothing superfluous only for you! Check it out!
You must be fully familiar with the standard and double-check it;
Decimals — should be uint8, totalSupply should be calculated correctly — there may be overflow for older versions of solidity;
Function and event signatures — check for presence and correctness, Including availability of returns (bool) and also including indexed for the address arguments in events;
Check the presence of events in functions, including in the constructor and preferably when burn occurs;
Transfer of 0 tokens should work;
Most tokens rely on an implementation from Open Zeppelin, so that: If _beforeTokenTransfer or _afterTokenTransfer are overridden, these functions must also be virtual and call super;
In some cases, project teams may deploy different token pairs of their project token and other tokens across various exchanges due to different requirements, more information about this case may be found in the following article:
uint a = balances[A];
uint b = balances[B];
balances[A] = a.sub(amount);
balances[B] = b.add(amount)
This also often occurs when there is a “transferMany” (sending to multiple addresses);
This is a description of issues of ERC20 token standard, which is widely used for Ethereum ICOs. From this article, you can learn about the problem of lost tokens:
Built-in transfer fee: Some tokens “charge a fee” when transferring. The logic is different, but the point is that the statement “the sender’s balance decreases by the specified amount, the recipient’s balance increases by the same amount” is not fulfilled;
No return: Solidity in some cases allows you to code without return. Sometimes it is simply forgotten in transfers. A variant of solving this is to call super.transfer without checking or throwing the return value;
Elastic Supply / Rebase / Storage fee: For some tokens, the user balance is recalculated over time. The totalSupply must be changed for each change. Mint/burn events are usually not possible, but can be reported;
Sometimes it is specifically required that the new or old allowance value be 0, but this is a tiny violation of the standard. This is how USDT and SafeERC20.safeApprove (now deprecated) behave. Now it is considered that in 99% of cases there can be no problems. But it is also possible to implement increase/decreaseAllowance or other non-standard methods;
A detailed review of tokens with unusual behavior pattern:
Permit allows you to issue an approve by signature. The standard is not easy — it is very detailed and describes a lot of important details. It should be checked very carefully. Often used for UX optimization (user can do with one transaction instead of two);
DAI supports its own version of Permit (not EIP2612). When working with USDT — consider that this token requires safeApprove, and the normal approve will break when issued from a non-zero value;
We should check that the contract checks the return value of transfer, transferFrom, approve:
From this article, you can learn about the problem of lost tokens:
uint oldB = token.balanceOf(address(this));
uint newB = token.balanceOf(address(this));
uint diff = newB - oldB;
<more logic with diff>
If has a call to an arbitrary address, you can re-enter this function from it. Because of this, the first call will terminate () with a twice changed newB;
A few months ago we finished our own research of Reentrancy attacks, in many ways, this article will rely on information from our old article so read it if you haven’t already:
It is common to first check balanceOf and/or allowance before calling transfer/transferFrom. This effectively duplicates the checks within the token itself. For the extra gas, we will at best get custom revert messages;
If the contract has .transferFrom, then users should not control the `from` parameter. Otherwise you can use other people’s appove’s and rob other users. (In 99% it should be `transferFrom(msg.sender, …)`) ;
ERC777 is one of the token standards with hooks on transfers. These hooks introduce a new attack vector which can potentially affect smart contracts that are not designed to handle additional calls during token transfers. This article provides a comprehensive explanation of ERC777, covering all the necessary details:
We hope that this article was informative and useful for you! Thank you for reading!
In recent months we have been actively developing our own Slither detectors (check out our Slitherin tool) to help with code review and audit process. Please let us know if you have discovered an issue/bug/vulnerability via our custom Slither detectors. You may contact us via opening a PR/Issue or directly, whichever is more convenient for you!
What instruments should we review? What would you be interested in reading about? Please leave your comments, we will be happy to answer them, and the best answers and questions may be included in the next article!