EIP-5920 A PAY instruction that sends only ether without executing code (PAY opcode)
Hegota Considered for Inclusion · EVM & execution · EIP status Draft · created 2022-03-14
Adds a new EVM instruction, PAY (0xfc), that sends only ether without executing the recipient's code. Its purpose is to avoid reentrancy attacks and unnecessary gas costs when sending funds.
Why it is needed
Today, for a smart contract to send ether to another address, it must call that address. A call hands the execution flow to the other party, so even if you only want to send money, the recipient's code runs too. This causes several problems. Reentrancy attacks are possible, where the recipient calls the sender back. Denial of service attacks are also possible, where the recipient uses up all the gas or reverts execution to block the transfer itself. The 2300 gas that comes with every call by default cannot modify regular storage, but it can modify transient storage (EIP-1153), which leaves room for unintended state changes. Running code also costs gas, so you pay more even if you only want to send funds. Future call-family instructions may have no way to limit the amount of gas passed on. Also, since EIP-7702 lets a regular account (EOA) delegate to code, even a transfer to an ordinary wallet address now executes code and uses gas. A dedicated instruction that only sends ether avoids all of these problems at once.
What changes
Today
- To send ether you must call the other address with CALL, and the other party's code runs along with it.
- You must guard against reentrancy, where the recipient calls the sender back, and against attacks that revert execution to block the transfer.
- Even if you only want to send funds, you pay gas for running the other party's code.
After EIP-5920
- A single PAY instruction sends only ether without executing the other party's code.
- The recipient's code does not run, so reentrancy or reverting cannot block the transfer.
- It returns 0 if the balance is insufficient and 1 on success, and incurs no cost for running the other party's code.
Who is affected
- Smart contract developers: Defenses against reentrancy and denial of service can be written more simply in transfer code. It can be used only after the hard fork.
- Client teams: A new opcode 0xfc and its gas calculation rules must be implemented. It cannot be implemented on networks where empty accounts still exist.
- Existing contracts: Receiving ether without a call becomes easier. The original text explains that this was already possible through SELFDESTRUCT or coinbase fees, so no invariant is broken.
- Ordinary users: There is little direct impact for users, but transfers sent by contracts become safer and use less gas.
In depth
Instruction definition and stack arguments
PAY uses opcode 0xfc. It pops addr (the recipient address) and val (the amount to send, in wei) from the stack in this order and moves val wei from the currently executing address to addr. The other party's code is never executed. The transfer happens only when the current address's balance is at least val. On success it pushes 1 onto the stack, and on failure it pushes 0. In the current specification, insufficient balance is the only case that produces 0. After execution, addr is added to accessed_addresses and becomes warm. There are only two arguments, addr and val, and unlike CALL there is no argument to specify the amount of gas to pass on.
Exceptional halt on static frames and invalid addresses
Execution halts with an exception in two cases. First, it halts if the current frame is a static frame as defined by EIP-214. Second, it halts if any of the upper 12 bytes of addr is nonzero, that is, if it is not a 20-byte address. CALL truncates the upper bytes in this case, but PAY deliberately halts. This design anticipates Address Space Extension. If truncation were allowed, contracts would come to depend on that behavior. If addresses are later extended beyond 20 bytes, PAY might fail to target that account or ether might go to the wrong address. The original text says that this halting behavior itself may change, so contracts should not depend on it, and that to halt intentionally they should use a dedicated mechanism such as INVALID (EIP-141).
Gas cost calculation
Gas is the sum of three items. First, if addr is in accessed_addresses it costs WARM_STORAGE_READ_COST, otherwise COLD_ACCOUNT_ACCESS_COST. These two constants follow the definitions in EIP-2929. Second, if the addr account already exists or val is 0, it costs 0, and otherwise GAS_NEW_ACCOUNT. Third, if val is 0 it costs 0, and otherwise GAS_CALL_VALUE. GAS_NEW_ACCOUNT and GAS_CALL_VALUE are taken as is from the frontier gas definitions in the execution layer specification (EELS). The original text states that PAY cannot be implemented on networks where empty accounts remain. For that reason it lists EIP-7523 as a prerequisite (requires).
Why the argument order matches CALL
PAY pops addr first and val next, in the same order as CALL. The original text gives one more reason besides consistency with CALL. With this order, PAY always appears right after COINBASE, which helps validators find MEV opportunities by pattern matching. The Rationale section of the original text covers only two topics, argument order and handling of invalid addresses, and gives no separate explanation for other choices.
Security considerations and backward compatibility
This change requires a hard fork. On security, the original text points out that existing contracts must not assume that their balance is under their own control alone. Ether can already be put into an account without a call through SELFDESTRUCT (partially restricted by EIP-6780), and ether can also arrive in an account against its owner's will through the priority fee sent to block.coinbase. The text concludes that PAY only makes this process cheaper and easier and does not break any existing invariant.
Current status and fork discussion
The status in the original text is Draft, so the specification may still change. The original text also says that the halting behavior on invalid addresses may change. In the core developer calls, EIP-5920 PAY opcode was received as a Hegotá (Hegota) candidate (PFI) at 08.13 ACDE #243 and moved up to Considered for Inclusion (CFI) for Hegotá at 10.08 ACDE #247. Complexity was rated medium, and most clients supported it with no objections. It has reached the CFI stage, and final inclusion has not been confirmed.