Today, when using Solidity to write smart contracts for the EVM, there are three ways to transfer some value of ether tokens between accounts:

  1. <address>.send
  2. <address>.transfer
  3. <address>.call{value} via the CALL opcode
Since 2019, it has been recommended to avoid using <address>.send and <address>.transfer, because these forward a fixed 2300 gas (and gas costs for common operations periodically change, forcing backwards incompatibility).

Across all three methods, execution context and control flow is passed to <address>, enabling reentrancy and Denial of service (DoS) attack vectors if a developer is not cautious.

Historically, this has led to billions of dollars of smart contract exploits.


EIP-5920 introduces a new opcode, PAY, that takes two stack parameters, addr and val, transferring val wei into addr, without calling any of its functions. In this way, developers can easily transfer ether without transferring execution context.


EIP Specification

Constants

ConstantDefinition
WARM_STORAGE_READ_COSTEIP-2929
COLD_ACCOUNT_ACCESS_COSTEIP-2929
GAS_NEW_ACCOUNTEELS
GAS_CALL_VALUEEELS

Behavior

A new opcode is introduced: PAY (0xf9), which:

  • Pops two values from the stack: addr then val
  • Transfers val wei from the current target address to the address addr
  • Marks addr as warm (adding addr to accessed_addresses)

Gas Cost

The gas cost for PAY is the sum of the following:

  • Is addr in accessed_addresses?
    • If yes, WARM_STORAGE_READ_COST;
    • Otherwise, COLD_ACCOUNT_ACCESS_COST.
  • Does addr exist or is val zero?
    • If yes to either, zero;
    • Otherwise, GAS_NEW_ACCOUNT.
  • Is val zero?
    • If yes, zero;
    • Otherwise, GAS_CALL_VALUE.

PAY cannot be implemented on networks with empty accounts (see EIP-7523).