EIP-5 Improving Gas Usage for RETURN and CALL* (Gas Usage for `RETURN` and `CALL*`)
Final · EVM & execution · EIP status Final · created 2015-11-22
Changes gas charging so that when a contract calls another contract, gas is charged only for the memory actually used at the point the call ends, not for the entire memory reserved in advance for output. This makes it possible to receive back strings and arrays whose length cannot be known in advance.
Why it is needed
In today's Ethereum Virtual Machine (EVM), when a contract calls another contract's function, it must decide the size of the space to receive the result in advance. However, the size of the result is often hard to know before actually making the call, since data in the other contract's storage cannot be seen directly from outside, and finding out its size would require yet another call. One option is to reserve a generously large space, but currently gas must be paid even for memory that goes unused, driving up the cost. As a result, receiving back strings or arrays of variable length is expensive and inconvenient, to the point of being practically unusable. This EIP solves both problems at once by charging gas only for memory that is actually used.
What changes
Today
- The output size must be fixed in advance before the call
- Reserving a large space means paying gas even for unused memory
- Receiving back variable-length data such as strings is practically impossible
After EIP-5
- Even if a very large output space is reserved, gas is charged only for what is actually used
- After the call, the size of the returned data can be found using the MSIZE opcode
- Strings and variable-length arrays can be received back flexibly
Who is affected
- Smart contract developers: It becomes possible to call functions that return strings and variable-length arrays, and to build proxy contracts that pass through the input and output of a contract whose interface is unknown.
- Client (VM implementation) teams: The implementation must be changed to grow memory and charge gas at two points around CALL.
- Existing deployed contracts: Gas costs no more than before, but code that allocated memory relying on MSIZE may see the memory addresses of its objects change.
In depth
Scope: CALL, CALLCODE, DELEGATECALL
The gas and memory rule changes apply to CALL, CALLCODE, and DELEGATECALL (the source groups these three together as CALL*). CREATE does not write to memory, so it is unaffected. The arguments of CALL* are gas, address, value, input_start, input_size, output_start, and output_size.
Charging Gas at Two Points in Time
At the start of the opcode, gas is charged only to grow memory up to input_start + input_size, and nothing is charged for output_start + output_size. If the called contract returns data of size n, the caller's memory grows up to output_start + min(output_size, n), and that much gas is charged at that point. The output is written into the range [output_start, output_start + min(n, output_size)). As a result, the caller can run out of gas at either the start or the end of the opcode.
MSIZE After the Call and the Proxy Pattern
After the call ends, the MSIZE opcode must return the size by which memory has actually grown. If the caller calls with output_start = MSIZE and output_size = 2**256-1, the region actually used becomes (output_start, MSIZE after the call), revealing the size of the returned data. Thanks to this property, there is no need to know the output size in advance, since it can be determined after the call, which makes it possible to build proxy contracts that pass through the input and output of a contract whose interface is unknown.
Design Alternatives and Rationale for the Chosen Approach
Alternatives considered included changing the opcode itself or the number of arguments, and changing the gas rule only when output_size equals 2**256-1. However, since the main implementation difficulty is having to grow memory at two points around CALL, a conditional approach does not make the implementation any simpler. Early on, there was also a proposal to push the size of the returned data onto the stack, but the MSIZE approach above was judged sufficient and far better for backward compatibility, so the current approach of changing the EVM as little as possible was chosen.
Backward Compatibility: Gas and Memory Size
Because contracts can read their remaining gas and memory size, this change could alter the meaning of existing contracts. On the gas side, however, the risk is low: the VM never charges more gas than before, and the contract can only end up refunding more gas to the caller. On the MSIZE side, there are two effects. First, the memory chunk that CALL was meant to reserve becomes smaller than before, which could overlap with later MSIZE-based allocations, though it is unlikely that such contracts exist. Second, the memory addresses of objects allocated via MSIZE change, but since objects should be written to not depend on absolute or relative position (that is, to be relocatable), this is generally not a problem.
Implementation Notes and Status
VM implementers must wait until the call completes and confirm that enough gas remains before growing memory. A typical usage is reserving 2**256-1 bytes for output. The original text includes links to the Python reference implementation before and after the change. This EIP is a standard finalized in Final status.