-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Description
Let's consider the following Solidity code:
function batch(address[] calldata to) external {
for (uint256 i = 0; i < to.length; i++) {
require(ERC20(address(0xdeadbeef)).transfer(to[i], 1 ether));
}
}This code should be deployable for production, but it isn't! This is because the gas complexity of this code is not linear with to.length. It's quadratic.
The complexity breaks because Solidity allocates memory (pushing the free memory pointer!) when decoding the returndata of the call, even for data that is not to be stored in memory. So, ERC20 returning bool will now push the free memory pointer by 32 bytes on each iteration. The total cost of memory allocation is quadratic, which increases the total gas complexity from linear to quadratic.
In order to fix this, one is forced to use Yul assembly rather than standard Solidity. This should never happen for such a common use case like batching simple operations.
Solidity should allocate memory (i.e. bump the free memory pointer) only if:
- the return variable is a memory variable, not a stack-based one; and
- the respective return value is used (as an argument or assigned to a variable)
Environment
- Compiler version: 0.8.31
- Compilation pipeline: IR
- Target EVM version: prague