EIP-211 New opcodes for querying return data ("New opcodes: RETURNDATASIZE and RETURNDATACOPY")

Final · EVM & execution · EIP status Final · created 2017-02-13

Adds two opcodes to the EVM: after a contract call finishes, the return data is held in a virtual buffer, with RETURNDATASIZE reading its size and RETURNDATACOPY copying its contents into memory.

Why it is needed

There are cases where the size of the data a counterparty contract will return cannot be known before the call finishes. In the existing EVM, the caller had to pre-specify the memory region to receive the return data, so if the size was unknown there was no way to receive the data in full. A workaround of making an extra call just to compute the size first is possible, but can be very expensive depending on the situation. A representative example is a general-purpose forwarding contract that inspects the call data it receives and passes it on unchanged to another contract. Such a contract does not know in advance which counterparty it will call, so without modifying the counterparty contract or repeating the call multiple times, there is no way to determine the return data size. Earlier similar proposals required gas to be set aside in advance at the call opcode, making both the spec and the implementation complex. This proposal follows the existing calldata handling approach as is, so the gas calculation is simple and it is 100 percent compatible with existing code.

What changes

Today

After EIP-211

Who is affected

In depth

Rules governing the return data buffer

Assume that each call frame of the EVM has a variable-size internal buffer called the return data buffer. The buffer is created empty for each new call frame. When a call-like opcode that creates a new call frame, such as CALL, CREATE, or DELEGATECALL, is executed, the buffer is first cleared (size 0), and when execution finishes, the entire return data (or the failure data from EIP-140) is stored in the caller's buffer, with the size updated accordingly. As an exception, CREATE and CREATE2 are treated as returning an empty buffer on success, and only store failure data on failure. If a call frame is not actually created because of insufficient balance for a transfer or because the target contract does not exist, the buffer ends up empty. Since at most one buffer is non-empty at any given time, implementations can optimize by sharing the buffer across call frames.

RETURNDATASIZE (0x3d)

Pushes the size of the return data buffer onto the stack. The gas cost is 2, the same as CALLDATASIZE.

RETURNDATACOPY (0x3e) and out-of-range handling

It behaves similarly to CALLDATACOPY, but it copies data from the return data buffer instead of the calldata. The handling of out-of-range access is the key difference. If start + length overflows or exceeds RETURNDATASIZE, the current call is aborted with an out-of-gas condition. Reading zero bytes right at the end of the buffer is allowed, but attempting to read even a single zero byte past the end of the buffer raises an exception. The gas cost is 3 + 3 * ceil(amount / 32), the same as CALLDATACOPY. The two opcodes were numbered inside the nibble block that already contains CALLDATASIZE and CALLDATACOPY.

Why a buffer approach

Other solutions allowing dynamic-length returns were also considered, but all of them required pre-charging gas at the call opcode, which complicated both the spec and the implementation. This proposal is very similar to the existing way calldata is handled, so it fits naturally into the EVM's conceptual model. The eWASM architecture already handles return data in exactly the same way.

Implementation cost and memory usage

EVM implementations must retain return data until the next call or until the current call returns. This resource is memory on the callee's side that has already been paid for in gas, so it adds no extra burden. Implementations can choose either to keep the callee's entire memory alive until the next call, or to copy only the return data into a separate memory region. Keeping the callee's memory alive until the next call-like opcode does not increase the maximum memory usage, because any memory allocation that happens in the caller's frame after the call returns can be moved ahead of the call with no change in gas cost, and that allocation would have counted toward the maximum allocation anyway.

Connection with EIP-140 (REVERT)

This makes EIP-140, which allows returning data on an intentional state revert, far more useful. Failure data can be larger than ordinary return data, or its size may not be known in advance at all; with this proposal, even after a CALL opcode signals failure, the failure data can be read out of the buffer regardless of whether the pre-allocated output area is large enough.

Activation timing and compatibility

This activates under the condition block.number >= BYZANTIUM_FORK_BLKNUM. In other words, it is a rule that took effect starting with the Byzantium hard fork. Other than adding two new opcodes, it changes no existing behavior at all, so it is fully backward compatible. This EIP's status is Final, meaning the spec is already settled and live on the network.

Original EIP · ← EIPs