Thanks. a good point.
but this is only for constant data. right?
Yes, so I am asking for a solution or different pattern so that upgradeable contracts are not worse off gas-wise because of this EIP compared to regular contracts when calling external functions on them. And by worse off I mean that the gas gap between upgradeable contracts and regular contracts is increased.
Iâm not saying that upgradeable contracts should require the same amount of gas as regular contracts when calling external functions on them. Iâm saying Iâd like there to be way to prevent increasing the gas gap that is currently done by this EIP.
I agree with @mudgen that it would be great if we could come up with an EIP that mitigate the issue of increase SLOAD cost for Proxy based contract.
It could for example be a mechanism by which a contract could update its bytecode.
But it could be other mechanisms too.
But I guess this is out of scope for this particular EIP.
I think the realistic long-term option is just charging per witness byte instead of per access, so SLOAD from contracts with only a small amount of storage would remain fairly cheap. I presume that these contracts that store these contract addresses per function would not have more than ~100 storage slots or so?
FYI: I actually came up with an trick to not use storage at all to get implementation address for proxy : https://twitter.com/wighawag/status/1306180888154251270
The implementation address is basically stored in an external contractâs bytecode that can self-destruct and being recreated at the same address with different bytecode (same creation code)
There is still an overhead (the call) but this is not dependent on SLOAD cost
EDIT: Note that it does not solve the proxy issue as CALL in EIP-2929 are even more expensive than SLOAD
Yes, that long-term solution sounds good. I was also hoping for a short-term solution.
How many storage slots are used per proxy pattern depends on the proxy pattern implementation. Diamonds currently have two reference implementations.
The diamond-1 and diamond-2 implementations requires 58 storage slots for 50 external functions.
The diamond-3 implementation requires between 59 to 232 storage slots for 50 external functions (depending on how many facets are used).
Info about the three diamond reference implementations is here: https://github.com/mudgen/diamond
The sets are transaction-context-wide, implemented identically to other transaction-scoped constructs such as the self-destruct-list and global
refund
counter. In particular, if a scope reverts, the access lists should be in the state they were in before that scope was entered.
In this case, when we talk about caching â it would be more reasonable to not clear the scope on reverts since it is still in the cache and access should still be cheaper. This would also reduce the memory requirements for the transaction as we would not have to keep up to 300 different states of the access lists.
In Nethermind, I think, we get it for three as for any account and storage we can get a single number with info when it was last time touched and at the same time each scope memorizes the position that can be compared with the âlast touchâ number. This is specific to our implementation and probably not readily available in every client.
Increasing gas costs of these operations would allow higher gas limits for the same uncle rate and state growth rate. A better way to view this change is that every other operation uses less gas, relatively.
This is not really going to help. Because if we implement this EIP, and then increase gas cost 3-4x to make the gas prices come down, such increase would negate the âprotectiveâ effect of this EIP as a DOS-attack deterrent. So the increase in gas limit needs to be much more conservative, if any. That is my main objection to the EIP - it only makes sense as a very short-term measure, but it introduces lots of extra long-term complexity. Not a good trade-off from my point of view
In this case, when we talk about caching â it would be more reasonable to not clear the scope on reverts since it is still in the cache and access should still be cheaper. This would also reduce the memory requirements for the transaction as we would not have to keep up to 300 different states of the access lists.
There are a couple of problems with this.
- Itâs IMO âsemantically uglyâ. A perfectly valid cheat, is that if you know that a transaction or scope exits on failure, itâs not necessary to actually execute it. It doesnât matter how or where it exits, as long as it exits.
- This became untrue back in the RipeMD touch OOG failure, since at that point a side-effect from within a reverting transaction did cause a side-effect, but thatâs the only exception we have.
- A perhaps larger problem, and definitely more practical than the one above, is that it introduces a way to cheat. Say you want to add
N
slots in contractA
to the access list.- Call
A
with0
gas (cost2600
).A
is now added to acess list. A now tries toSLOAD
slot1
. Fails onOOG
. You now haveA, (A:1)
in access list - Call
A
again with0
gas (cost100
).A
now tries toSLOAD
slot1
. Fails onOOG
. You how haveA, (A:1), (A:2)
in access list.
- Call
So basically, you can add things to your access list at a cost of ~100 gas, instead of 2K+
it would be more reasonable to not clear the scope on reverts since it is still in the cache and access should still be cheaper.
In the scenario above, the failing SLOAD
s would not place the item in the cache, since the cost-check should be done before the item is loaded.
The intention behind clearing the scope is that we already have code paths for how to implement transaction-scoped variables (namely, the refunds counter and the selfdestructs list), and itâs cleaner and simpler if we just reuse those code paths for all the transaction-scoped variables that we do now and in the future than if we start customizing revert rules depending on whatâs optimal for each specific situation.
Do I understand it correctly that calling any account that has not been called before costs at least 2600 gas. Assuming this is correct it will not only increase the price gap, but break any contract that uses transfer
or send
. The mentioned reports focus only on the receivers that use too much gas, but I would say this might need to be reevaluated, as it is not even possible to perform the solidity call for transfer
or send
to an EOA. While I see that EIP-2930: Optional access lists can prevent this from happening, but this requires that wallets and dapps support this.
There is a solidity issue to remove these limits from the compiler (Remove .send and .transfer. ¡ Issue #7455 ¡ ethereum/solidity ¡ GitHub), but I think this is still a quite open discussion.
Looking at all of this I have to agree with Alexey
That is my main objection to the EIP - it only makes sense as a very short-term measure, but it introduces lots of extra long-term complexity. Not a good trade-off from my point of view
Edit: correct link
There are a couple of problems with this.
I see. This can be prevented by changing the access list after successful execution of SLOAD / SSTORE.
Not reverting changes on failing calls would be transaction scoped but not call-scoped.
The destroy lists and refund counters are transaction-scoped but also have the call scoped clones that are committed on exiting a call.
I think my approach actually simplifies the code on the client side (at least in Nethermind). This EIP is quite complex by itself.
Call scope is cloned up to 300 times on deep calls.
Do I understand it correctly that calling any account that has not been called before costs at least 2600 gas. Assuming this is correct it will not only increase the price gap, but break any contract that uses
transfer
orsend
That is correct. Most transactions will not break, it seems, but some will require more gas. A transaction which does a lot of âreaching out to new placesâ will cost more, but transactions which âinteracts with few other contracts multiple timesâ will cost less.
While I see that EIP-2930: Optional access lists can prevent this from happening, but this requires that wallets and dapps support this.
Yes, it does. I personally donât think 2929 is going to go live without 2930, for specifically that reason. However, 2930 doesnât require widespread support, if the only usecase is to be able to salvage a tiny subset of txs that would otherwise fail. As long as there are some signers/wallets that support it, thatâs enough to make it work.
Because if we implement this EIP, and then increase gas cost 3-4x to make the gas prices come down, such increase would negate the âprotectiveâ effect of this EIP as a DOS-attack deterrent.
Yeah I donât see why we would increase the gas cost to 3-4x. Experimentation shows that the actual gas spending doesnât change more than a few percent. In other words: an 8M block pre-2929, is still an 8M block post-2929.
The destroy lists and refund counters are transaction-scoped but also have the call scoped clones that are committed on exiting a call.
Geth doesnât operate like that. We did, once upon a time, and even used clone-copies of the state. That bit us hard in the shanghai attacks, and we eventually had to implement journalling instead. I donât agree that clone-commit/discard is easier or better.
Do I understand it correctly that calling any account that has not been called before costs at least 2600 gas. Assuming this is correct it will not only increase the price gap, but break any contract that uses
transfer
orsend
I donât understand this argument. It would only break contracts that use transfer
or send
if those contracts themselves are called by other contracts and those other contracts make the call with a fixed gas limit (a very rare pattern nowadays). If you mean a contract calling send
to immediately forward received funds inside the pre-allotted 2300 gas, I donât think thatâs possible anyway because value-transferring calls have a gas cost of 9000.
those other contracts make the call with a fixed gas limit (a very rare pattern nowadays)
This is the current implementation in Solidity (setting the gas limit to the stipend) as far as I remember (I would need to read up again on the concrete limits used by solidity).
My main point was that the previous analysis done for EIP-1884 are not 1:1 valid for this EIP and I would love if something similar would be done again.
Edit: Just to avoid that I am misunderstanding something, afaik EIP-1884 only had a breaking effect when the receiving contract did additional logic (e.g. a proxy or emitting events) â this would result in an OOG is the receiver contract. This EIP would break in the calling contract (with an OOG). Would that be correct?
Edit 2: Stipend docs from Solidity â Security Considerations â Solidity 0.7.1 documentation
Here I have interesting case for a CALL
with non-zero value.
- The cold destination
D
is added toaccessed_accounts
. - It turns out
D
does not exist: additional penalty of 25000. - This penalty causes out-of-gas exception.
Now we repeat the above steps. The difference is D
is warm now although it still does not exist.
Thereâs nothing that says something in the accesslist must exist (e.g. 2930 can add whatever that doesnât exist).
So it shouldnât be an edgecase (as in, semantically itâs pretty clear-cut, IMO), but it may very well be, so good catch. We should make a test for this, and Iâll also try it out on Yolov3.
Actually, you can simplify this case by not relying on 25000 penalty. The call can simply fail because of the cold account access penalty of 2600. Still the D
will be added to the accessed_accounts
on the first call.
I donât understand all consequences of this, but try exploring this mechanism as a way to bypass the cold account access cost. For a series of failing calls to non-existing account D
only the first one will consider the D
cold. But in practice, a node implementation must do full D
lookup unless it maintain a cache of non-existing accounts. Of course, these failing calls must be wrapped with another calls to proceed.
I think this would work âsaferâ if the 2600 gas is strictly required up front to proceed with any call, independently of the actually initial call cost computed later. Similarly to the required minimal gas amount to proceed with a SSTORE
.