34.SWC-134_hardcoded gas amount
2023-07-13 16:12:52 # 09.SWC

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
pragma solidity 0.6.4;

interface ICallable {
function callMe() external;
}

contract HardcodedNotGood {

address payable _callable = 0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa;
ICallable callable = ICallable(_callable);

constructor() public payable {
}

function doTransfer(uint256 amount) public {
_callable.transfer(amount);
}//2300 gas limited

function doSend(uint256 amount) public {
_callable.send(amount);
}//2300 gas limited

function callLowLevel() public {
_callable.call.value(0).gas(10000)("");
}//hardcoded gas amount

function callWithArgs() public {
callable.callMe{gas: 10000}();
}//hardcoded gas amount
}

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()

link

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
contract Vulnerable {
function withdraw(uint256 amount) external {
// This forwards 2300 gas, which may not be enough if the recipient
// is a contract and gas costs change.
msg.sender.transfer(amount);
}
}

contract Fixed {
function withdraw(uint256 amount) external {
// This forwards all available gas. Be sure to check the return value!
(bool success, ) = msg.sender.call.value(amount)("");
require(success, "Transfer failed.");
}
}

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. 【看我之前的文章,不做赘述了】

Prev
2023-07-13 16:12:52 # 09.SWC
Next