SWC-134_hardcoded gas amount
The transfer()
and send()
functions forward a fixed amount of 2300 gas. Historically, it has often been recommended to use these functions for value transfers to guard against reentrancy attacks. However, the gas cost of EVM instructions may change significantly during hard forks which may break already deployed contract systems that make fixed assumptions about gas costs. For example. EIP 1884 broke several existing smart contracts due to a cost increase of the SLOAD instruction.
1 | pragma solidity 0.6.4; |
remediation: avoid the use of transfer()
and send()
and do not otherwise specify a fixed amount of gas when performing calls. Use .call.value(...)("")
instead. Use the checks-effects-interactions pattern and/or reentrancy locks to prevent reentrancy attacks.
stop using transfer()
It looks like EIP 1884 is headed our way in the Istanbul hard fork. This change increases the gas cost of the SLOAD
operation and therefore breaks some existing smart contracts.
Those contracts will break because their fallback functions used to consume less than 2300 gas, and they’ll now consume more. Why is 2300 gas significant? It’s the amount of gas a contract’s fallback function receives if it’s called via Solidity’s transfer()
or send()
methods. 1
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()
and send()
be avoided.
Gas Costs Can and Will Change
Each opcode supported by the EVM has an associated gas cost. For example, SLOAD
, which reads a word from storage, currently—but not for long—costs 200 gas. The gas costs aren’t arbitrary. They’re meant to reflect the underlying resources consumed by each operation on the nodes that make up Ethereum.
Smart Contracts Can’t Depend on Gas Costs
If gas costs are subject to change, then smart contracts can’t depend on any particular gas costs.
Any smart contract that uses transfer()
or send()
is taking a hard dependency on gas costs by forwarding a fixed amount of gas: 2300.
Our recommendation is to stop using transfer()
and send()
in your code and switch to using call()
instead:
1 | contract Vulnerable { |
Other than the amount of gas forwarded, these two contracts are equivalent.
What About Reentrancy?
This was hopefully your first thought upon seeing the above code. The whole reason transfer()
and send()
were introduced was to address the cause of the infamous hack on The DAO). The idea was that 2300 gas is enough to emit a log entry but insufficient to make a reentrant call that then modifies storage.
Remember, though, that gas costs are subject to change, which means this is a bad way to address reentrancy anyway. Earlier this year, the Constantinople fork was delayed because lowering gas costs caused code that was previously safe from reentrancy to no longer be.
If we’re not going to use transfer()
and send()
anymore, we’ll have to protect against reentrancy in more robust ways. Fortunately, there are good solutions for this problem. 【看我之前的文章,不做赘述了】