Thursday comes with a new part of All About Smart Contract Bugs & Security ? A cakewalk series. In this blog, we talk about how the transfer() function in smart contracts can become the root cause of re-entrancy, changing the whole meaning of a smart contract.
We often use functions thinking that they will make our job easier and in search of such functions, we end up using the ones that open multiple backdoors for attackers. One such function is the transfer() function in Solidity. Let’s see how it tampers with the security of a smart contract.
What does transfer() do in Solidity?
Table of Contents
This method appeared in the later developments of Solidity versions. The details worth mentioning about it are:
- First, this method has a 2300 gas limit. However, later on, the developers decided to add the .gas() modifier, which redefines the limit of the provided gas.
- Second, unlike the send() method, transfer() throws an exception when performed unsuccessfully. Thus, letting the user know when the transaction has failed right at the execution attempt.
It sounds just about right to be a useful method indeed.
Ever since its introduction, transfer() has typically been recommended by the security community because it helps guard against reentrancy attacks. This guidance made sense under the assumption that gas costs wouldn’t change, but that assumption turned out to be incorrect. We now recommend that transfer() be avoided as gas costs can and will change.
And if gas costs are subject to changes, then smart contracts can not depend on any particular gas costs. A smart contract making use of transfer() is taking a hard dependency on gas costs by forwarding a fixed amount of gas i.e., 2300.
Real-World Example: The Vulnerability Behind Ethereum’s Rollback of Constantinople
The whole reason transfer() was introduced was to address the cause of the infamous hack on The DAO. The idea behind it was that 2300 gas is enough to emit a log entry but insufficient to make a reentrant call that then modifies storage. But as we already mentioned above, that gas costs are subject to change, implying this is a bad way to address re-entrancy anyway.
Constantinople, Ethereum’s upgrade in 2019, introducing cheaper gas costs for certain SSTORE operations suffered a major setback when, as an unwanted side effect, the smart contract enabled re-entrancy attacks when using address.transfer() because lowering gas costs caused code that was previously safe from reentrancy to no longer be.
We recommend stopping using the transfer() in your code and switching to using call() instead, the most customized method of all.
Let’s take a look at the code:
Its principal difference from transfer() is an opportunity to set a gas limit via .gas(gasLimit) modifier. It is necessary in case the payable function of the contract receiving ether performs a complex logic that requires plenty of gas.
Note: Other than the amount of gas forwarded, these two contracts are equivalent.
That was our take on Solidity’s transfer() function and how it can cause your smart contract to be vulnerable against a re-entrancy attack.
And with that, we wrap up this blog series; we hope this helps you safeguard your smart contracts against such vulnerabilities.
To know more about us and our services, do visit our website: https://www.immunebytes.com/, and connect with the ImmuneBytes team to get your smart contract free of any such vulnerabilities and loopholes.