Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7069: Revamped CALL instructions

Introduce EXTCALL, EXTDELEGATECALL and EXTSTATICCALL with simplified semantics

Authors Alex Beregszaszi (@axic), Paweł Bylica (@chfast), Danno Ferrin (@shemnon), Andrei Maiboroda (@gumb0), Charles Cooper (@charles-cooper)
Created 2023-05-05
Discussion Link https://ethereum-magicians.org/t/eip-revamped-call-instructions/14432
Requires EIP-150, EIP-211, EIP-214, EIP-2929

Abstract

Introduce three new call instructions, EXTCALL, EXTDELEGATECALL and EXTSTATICCALL, with simplified semantics. Introduce another instruction, RETURNDATALOAD for loading a word from return data into stack. The existing call instructions remain unchanged.

The new instructions do not allow specifying a gas limit, but rather rely on the “63/64th rule” (EIP-150) to limit gas. An important improvement is the rules around the “stipend” are simplified, and callers do not need to perform special calculation whether the value is sent or not.

Furthermore, the obsolete functionality of specifying output buffer address is removed in favor of using RETURNDATACOPY instead. For cases which would previously *CALL output into a buffer and then MLOAD from the buffer, RETURNDATALOAD is provided instead.

Lastly, instead of returning a boolean for execution status, an extensible list of status codes is returned: 0 for success, 1 for revert, 2 for failure.

We expect most new contracts to rely on the new instructions (for simplicity and in order to save gas), and some specific contracts where gas limiting is required to keep using the old instructions (e.g. ERC-4337).

Motivation

Observability of gas has been a problem for very long. The system of gas has been (and likely must be) flexible in adapting to changes to both how Ethereum is used as well as changes in underlying hardware.

Unfortunately, in many cases compromises or workarounds had to be made to avoid affecting call instructions negatively, mostly due to the complex semantics and expectations of them.

This change aims to remove gas observability from the new instructions and opening the door for new classes of contracts that are not affected by repricings. Furthermore, once the EVM Object Format (EOF) is introduced, the legacy call instructions can be rejected within EOF contracts, making sure they are mostly unaffected by changes in gas fees. Because these operations are required for removing gas observability they will be required for EOF in lieu of the existing instructions.

It is important to note that starting Solidity 0.4.21, the compiler already passes all remaining gas to calls (using call(gas(), ...), unless the developer uses the explicit override ({gas: ...}) in the language. This suggests most contracts don’t rely on controlling gas.

Besides the above, this change introduces a convenience feature of returning more detailed status codes: success (0), revert (1), failure (2). This moves from the boolean option to codes, which are extensible in the future.

Lastly, the introduction of the RETURNDATA* instructions (EIP-211) has obsoleted the output parameters of calls, in a large number of cases rendering them unused. Using the output buffers have caused “bugs” in the past: in the case of ERC-20, conflicting implementations caused a lot of trouble, where some would return something, while others would not. With relying on RETURNDATA* instructions this is implicitly clarified. This proposal also adds the “missing” RETURNDATALOAD instruction to round out returndata buffer access instructions.

Specification

Name Value Comment
WARM_STORAGE_READ_COST 100 From EIP-2929
COLD_ACCOUNT_ACCESS 2600 From EIP-2929
CALL_VALUE_COST 9000  
ACCOUNT_CREATION_COST 25000  
MIN_RETAINED_GAS 5000  
MIN_CALLEE_GAS 2300  

We introduce four new instructions:

  • EXTCALL (0xf8) with arguments (target_address, input_offset, input_size, value)
  • EXTDELEGATECALL (0xf9) with arguments (target_address, input_offset, input_size)
  • EXTSTATICCALL (0xfb) with arguments (target_address, input_offset, input_size)
  • RETURNDATALOAD (0xf7) with argument offset

Execution semantics of EXT*CALL:

  1. Charge WARM_STORAGE_READ_COST (100) gas.
  2. Pop required arguments from stack, halt with exceptional failure on stack underflow.
    • NOTE: When implemented in EOF, stack underflow check is done during stack validation and runtime check is omitted.
  3. If value is non-zero:
    • Halt with exceptional failure if the current frame is in static-mode.
    • Charge CALL_VALUE_COST gas.
  4. Peform (and charge for) memory expansion using [input_offset, input_size].
  5. If target_address is not in the warm_account_list, charge COLD_ACCOUNT_ACCESS - WARM_STORAGE_READ_COST (2500) gas.
  6. If target_address is not in the state and the call configuration would result in account creation, charge ACCOUNT_CREATION_COST (25000) gas.
    • The only such case in this EIP is if value is non-zero.
  7. Calculate the gas available to callee as caller’s remaining gas reduced by max(floor(gas/64), MIN_RETAINED_GAS).
  8. Fail with status code 1 returned on stack if any of the following is true (only gas charged until this point is consumed):
    • Gas available to callee at this point is less than MIN_CALLEE_GAS.
    • Balance of the current account is less than value.
    • Current call stack depth equals 1024.
  9. Perform the call with the available gas and configuration.
  10. Push a status code on the stack:
    • 0 if the call was successful.
    • 1 if the call has reverted (also can be pushed earlier in a light failure scenario).
    • 2 if the call has failed.
  11. Gas not used by the callee is returned to the caller.

Execution semantics of RETURNDATALOAD:

  1. Charge G_verylow (3) gas
  2. Pop 1 item from the stack, to be referred to as offset
  3. If offset + 32 > len(returndata buffer), halt with exceptional failure.
  4. Push 1 item onto the stack, the 32-byte word read from the returndata buffer starting at offset.

Rationale

Removing gas selectability

One major change from the original CALL series of instructions is that the caller has no control over the amount of gas passed in as part of the call. The number of cases where such a feature is essential are probably better served by direct protocol integration.

Removing gas selectability also introduces a valuable property that future revisions to the gas schedule will benefit from: you can always overcome Out of Gas (OOG) errors by sending more gas as part of the transaction (subject to the block gas limit). Previously when raising storage costs (EIP-1884) some contracts that sent only a limited amount of gas to their calls were broken by the new costing.

Hence some contracts had a gas ceiling they were sending to their next call, permanently limiting the amount of gas they could spend. No amount of extra gas could fix the issue as the call would limit the amount sent. The notion of a stipend floor is retained in this spec. This floor can be changed independent of the smart contracts and still preserve the feature that OOG halts can be fixed by sending more gas as part of the transaction.

Stipend and 63/64th rule

The purpose of the stipend is to have enough gas to emit logs (i.e. perform non-state-changing operations) when a “contract wallet” is called. The stipend is only added when the target has code and the call value is non-zero.

The 63/64th rule has multiple purposes:

a. to limit call depth, b. to ensure the caller has gas left to make state changes after a callee returns.

Additionally there is a call depth counter, and calls fail if the depth would exceed 1024.

Before the 63/64th rule was introduced, it was required to calculate available gas semi-accurately on caller side. Solidity has a complicated ruleset where it tries to estimate how much it will cost on the caller side to perform the call itself, in order to set a reasonable gas value.

We have changed the ruleset:

The 63/64th rule is still applied, but - At least MIN_RETAINED_GAS gas is retained prior to executing the callee, - At least MIN_CALLEE_GAS gas is available to the callee.

Removing the call stack depth check was initially considered, but this would be incompatible with the original *CALL instructions, as well as CREATE* instructions, which can be intertwined with the new EXT*CALL instructions in the call stack. As such, keeping the call stack depth check involves no change affecting legacy code.

Also, we find the simple (as opposed to the complex 63/64th rule) hard cap reassuring, that the call stack depth is limited, in case the gas rules can be bypassed. Lastly, the amount of gas to reach depth of 1024 is huge, but not absurdly huge, and we want to avoid constraining ourselves by dependency of this check on current gas limits.

Output buffers

The functionality of specifying output buffer address is removed, because it is added complexity and in a large number of cases implementers prefer to use RETURNDATACOPY instead. Even if they rely on the output buffer (like in the case of Vyper), they would still check the length with RETURNDATASIZE. In Solidity one exception is the case when the expected return size is known (i.e. non-dynamic return values), in this case Solidity still uses the output buffer. For these cases, RETURNDATALOAD is introduced, which simplifies the workflow of copying returndata into a (known) output buffer and using MLOAD from there; instead, RETURNDATALOAD can be used directly.

Status codes

Current call instructions return a boolean value to signal success: 0 means failure, 1 means success. The Solidity compiler assumed this value is a boolean and thus uses the value as branch condition to status (if iszero(status) { /* failure */ }). This prevents us from introducing new status codes without breaking existing contracts. At the time of the design of EIP-211 the idea of return a specific code for revert was discussed, but ultimately abandoned for the above reason.

We change the value from boolean to a status code, where 0 signals success and thus it will be possible to introduce more non-success codes in the future, if desired.

Status code 1 is used for both reverts coming from the callee frame and light failures encountered in the execution of the instructions. The reason for combining them is keeping the semantics similar to the original CALLs - both scenarios preserve unused gas and continue being indistinguishable to the caller.

Parameter order

The order of parameters has been changed to move the value field to be the last. This allows the instructions to have identical encoding with the exception of the last parameter, and simplifies EVM and compiler implementations slightly.

Opcode encoding

Instead of introducing three new EXT*CALL opcodes we have discussed a version with an immediate configuration byte (flags). There are two main disadvantages to this:

  1. Some combination of flags may not be useful/be invalid, and this increases the testing/implementation surface.
  2. The instruction could take variable number of stack items (i.e. value for EXTCALL) would be a brand new concept no other instruction has.

It is also useful to have these as new opcodes instead of modifying the existing CALL series inside of EOF. This creates an “escape hatch” in case gas observability needs to be restored to EOF contracts. This is done by adding the GAS and original CALL series opcodes to the valid EOF opcode list.

CALLCODE

Since CALLCODE is deprecated, we do not introduce a counterpart here.

Backwards Compatibility

No existing instructions are changed and so we do not think any backwards compatibility issues can occur.

Security Considerations

It is expected that the attack surface will not grow. All of these operations can be modeled by existing operations with fixed gas (all available) and output range (zero length at zero memory).

When implemented in EOF (where the GAS opcode and the original CALL operations are removed) existing out of gas attacks will be slightly more difficult, but not entirely prevented. Transactions can still pass in arbitrary gas values and clever contract construction can still result in specific gas values being passed to specific calls. It is expected the same surface will remain in EOF, but the ease of explotation will be reduced.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Alex Beregszaszi (@axic), Paweł Bylica (@chfast), Danno Ferrin (@shemnon), Andrei Maiboroda (@gumb0), Charles Cooper (@charles-cooper), "EIP-7069: Revamped CALL instructions [DRAFT]," Ethereum Improvement Proposals, no. 7069, May 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7069.