The zero address should not have a nonzero balance.
It already does. And adding a single special case for reversion just replaces one class of potential bugs with another. If an address gets set to zero and is unchangeable, then any function that calls PAY on that address will always revert and effectively be uncallable. If that function needs to be called as some necessary phase of the contract, then your contract is now permanently frozen and the ETH is inaccessible.
E.g. imagine a lottery contract where people can set their payout address when they buy a ticket. A function called startNewRound() checks if the last round is over, PAYs out ETH to the winner, and starts the next round anew. If the winner sets their payout address to 0, then startNewRound() will always revert when attempting to make the payout, and the lottery is now frozen with no future rounds possible.
It makes PAY a lot easier to reason about if it succeeds no matter what, as long as you pass in a valid address and ETH amount. If you want your contract to prevent payouts to the zero address, just add in some custom check like require(addr != address(0)). This is semantically equivalent to how people already make payouts with call(), transfer(), and send(), and there is no reason to change it for just this single opcode. Even less so, in fact, since the point of PAY is pretty much to bypass any checks and force an address to receive ETH.
That makes sense. Whatâs currently written probably makes more sense then.
burning for payments to address(0) is still a bad idea for a few reasons:
- when a contract PAYs to some address, it might operate under the assumption that the balance of that address will increase. E.g. the contract PAYs some amount ether to some receiving address, then at a later point checks that the receiving addressâs balance has increased by that amount. If the receiver is address(0), then that check will fail, even though the payment was made.
- the existing CALL opcode does not burn ETH when the recipient is address(0); changing the semantics of ETH transfers for this new opcode will be confusing
- itâs pointless; the only reason I can think of for someone to burn ETH is executing a malicious attack using the situation described in (1)
Again, itâs best for no exceptions to be made for the zero address.
Why would that possibly need to be done?
The call still fails.
The call still fails.
Just checked on Goerli, and CALL does not fail when the receiver is address(0). Hereâs my tx sending 0.01 ETH there from a contract:
https://goerli.etherscan.io/tx/0x785a1484424779a040b3c6213e01f162b43c5fc4c5d9780d8ed117d332b595b4
And it increased the balance of address(0) from â11,090.383âŚâ to â11,090.393âŚâ, so no burn. So to bring the opcodes semantically in-line would require not giving address(0) any special treatment.
Why would that possibly need to be done?
Dunno, but someone might still end up doing it. Like, if thereâs a bunch of complicated conditional code that decides whether or not to make a payment, so the dev decides that the simplest way to check whether or not the payment was made is just to query the receiverâs balance. My point stands that a burn is pointless, and (hypothetically at least) could lead to bugs.
Never mind then. It seems that the special case is not needed.
Since this EIP is now CFId, I would like a clarfication if this new âburn ruleâ for the zero address still holds. It seems that there is confusion if it is ok to transfer eth to the zero address, and this is ok, and it behaves just as normal. But this EIP introduces a rule to burn the ethâŚ? Is this still desired, or is this rule going to be removed (I have no strong opinion here).
I also think that the gas section is not clear. From current EIP:
Gas pricing
The gas pricing is that of a
CALL
with a positivemsg.value
, but without any memory expansion costs or âgas sent with callâ costs, with a gas reduction of500
to compensate for the reduced amount of computation.
If we review CALL
regarding the relevant gas costs for PAY
: the CALL
gas price currently depends on: do we transfer value (9000), is it warm or cold (100 for warm, 2600 for cold), and do we create a new account (25000).
I do not see how this has a gas reduction of 500
in any situation here, to reach the base gas costs of the PAY
opcode of 3000.
At this point I do not understand how to implement the gas costs
Clearly I forgot to fix the pricing. The cost of the PAY
opcode should 100 for warm addresses, 2600 for cold addresses, and 25000 for new accounts, and should have a base gas cost of 9000.
I clearly forgot to push quite a few changes. Fixed.
Given that EIP 6780 is included in Cancun, and it preserves the unconditional send aspect of SELFDESTRUCT
. It seems that the functionality of this EIP can be implemented as a contract with low gas overhead.
While this is true, it creates unnecessary work for the EVM. Additionally, âsending etherâ is such a common task for contracts, and the behavior of selfdestruct is still very much TBD.
I think EOF needs PAY
for security if it wonât have zero-gas CALL
.
Itâs useful security-wise to be able to block re-entry on an ether payment. This is one of the reasons that the callvalue stipend is not enough gas to SSTORE or call with value. While re-entry can be protected with TLOAD
/TSTORE
, simple ether payments are the main reason I still use less than GAS
for the gas parameter of CALL
. Especially with eip-7702 you do not want to grant control to the recipient just to pay them. They might even be an account unknown to you, as is the case for COINBASE
payments.
PAY
is in the spirit of EOF because it doesnât have a gas parameter. It doesnât enable anything not already possible, because it is still possible to pay an account without its permission via COINBASE
priority fees.
The workaround is to EXTCALL
the value into a legacy contract and CALL
with 0
gas. The overhead is 9000 gas. But PAY
is strictly better because there is only one value transfer.
This aspect is still preserved by COINBASE
priority fees. It is impossible to reject these fees and you arenât notified.
@Pandapip1 Please update the opcode so it does not conflict with one of the ones currently used by EOF.
I proposed this EIP for inclusion into Prague. But client teams blocked it without any discussion. I think @shemnon put together a piece summarizing each client teamâs position on all of the Prague EIPs, but I canât find it offhand.
What we were lacking was the rationale for why it was needed, what hole it filled. It wasnât until last night that the gas=0 for CALL
pattern was shared, and how gas introspection broken that. So, that was new information material to the opcodes utility.
Last year we were by default resistant to new additions that didnât fill holes due to pectra scheduling pressure.
We added it in the call this morning to the universally nebulous âpost eof-devnet-1â bucket because we donât have much clarity on when Fusaka will ship. Once we get devnet-1 shipped we can determine where it lands. This bucket includes Osaka proposed EIPs as well as EIPs not proposed for Osaka and likely targeting Amsterdam.
If inclusion in Osaka is a goal, then this request should also be following the EIP-7723 process, and you or @wjmelements should post a PR to the osaka meta (EIP-7607) and attend the next two ACDE calls where my understanding is we will set and freeze Osaka scope. Be aware that the vibe is that all new EIPs proposed for Fusaka not already known is âdefer to Amsterdamâ at this point, and that advocacy will need to overcome this position. The door is not closed yet, at least not officially.
Opcode update, also mentioning EXTCALL and priority fees: Update EIP-5920: Update PAY Opcode, Motivation, and Rationale by wjmelements ¡ Pull Request #9389 ¡ ethereum/EIPs ¡ GitHub
Sharing my presentation for this weekâs ACDE here:
Copying the address halting behavior from EXTCALL (EIP-7069):
And that was brought up where? What is the point of requiring EIP authors to post to ethereum magicians if these objections wonât actually be discussed in ethereum magicians. By the way, the rationale has already been covered in this thread; and is also right there in the EIP:
Currently, to send ether to an address requires you to call into that address, which transfers execution context to that address, which creates several issues:
- First of all, it opens a reentrancy attack vector, as the recipient can call back into the sender. More generally, the recipient can unilaterally execute arbitrary state changes, limited only by the gas stipend, which is not desirable from the point of view of the sender.
- Secondly, it opens a DoS vector. Contracts which want to send ether must be cognizant of the possibility that the recipient will run out of gas or revert.
- Finally, the
CALL
opcode is needlessly expensive for simple ether transfers, as it requires the memory and stack to be expanded, the recipientâs full data including code and memory to be loaded, and finally needs to execute a call, which might do other unintentional operations. Having a dedicated opcode for ether transfers solves all of these issues, and would be a useful addition to the EVM.
It seems like every other ACD call, client teams wonder why engagement from application and compiler teams is low. This is why â in order to advocate for changes you need to attend hours of calls (and most of the content is irrelevant to outsiders), and in the end, the changes you advocate for can and will be rejected by an opaque, backroom process with little-to-no feedback.
I was trying to point out how it fits into the current EOF planâwhat specific gap it addresses. The reasons you mentioned are more general benefits, not tied directly to EOF. What stood out as new was the gas=0 use case, which wasnât included in the rationale you pulled from the EIP.
When it comes to proposing new opcodes, I often bring up TSTORE/TLOAD as a great example. The person championing it wasnât a developer from a client team, yet they still managed to provide implementations across all four clients, along with solid reference tests (including pre-EEST tests). Client developers are usually stretched thin, so showing up with working implementations and strong tests is often the best way to get them to seriously evaluate an EIP on its own merits. From what Iâve seen, the one time this approach was used, it got included in the fork after the current one under consideration.