In the former case, 20k gas was charged when the slot was first touched. In the latter case, only 5k was charged. In both cases the refund serves to zero that out.
As far as I understand, the reason why someone would keep changing the same storage position in the middle of a transaction (instead of using memory or stack) is the desire to communicate between different frames of the transaction in a way which is not input/outputs of a CALL. Crucially, it allows such communication across the intermediate invocations of a different, potentially untrusted contract (see example of Reetrancy Locks above).
I wonder if this is the best way to handle such communication. Perhaps it is better to have a pair of opcodes like TSTORE and TLOAD that will write Transitent storage, which survives between the frames of one transaction, but does not get persisted? And these opcodes will have flag gas cost?
I can see value in transaction storage @AlexeyAkhunov, though I still think there is value in this EIP as it stands since there are some situations where you actually do want to work with storage data multiple times in a single contract call.
@MicahZoltu The reason I am looking for alternatives to this is because I know that gas metering is going to become more complicated with this EIP, especially with the support of pre-EIP and post-EIP logic in the same code. I think if you want to work with storage data multiple times, you can either pay the high cost of SSTORE, or do stuff in transient storage, and then “commit it” to permanent if you want. Oh, now you gave me the idea for TCOMMIT opcode
I did consider something like this, but using sstore/sload has several advantages:
- It uses an existing mechanism that is already in use for this purpose.
- It introduces minimal additional complication to the EVM.
- It also reduces excess gas costs in other situations where multiple changes are made to storage.
One example is ERC20-based contracts, where you might want to issue an approval that’s then used in the same transaction.
I think introducing new opcodes for this would be significantly more complex, particularly given the fact that clients are generally already tracking ‘dirty’ properties right now. It would also require new language support.
Implementing this pattern is quite complex, especially across multiple contracts, and impossible in some cases such as when you don’t know if this will be the last time your contract is called or not within the current transaction.
I am not very sure of that. It might actually be cleaner to implement this as a new opcode pair, because the current code for storage gas accounting stays the same. And implementation is basically the same as memory, with the difference that it is the same copy for all the frames. Gas cost could be exactly the same as for memory right now, without any refunds and references to transaction boundaries.
As for the language support, I can see how it can be done for Solidity, for example, quite easily - you declare variable like storage variable, but use modifier “transient” or something like that.
Generally, I am not for tweaking the existing instructions to be more “versatile” - but instead for having perhaps more instruction with simpler semantics.
I am going to write an EIP for transient storage opcodes and see what happens there
That doesn’t seem particularly persuasive - instead clients will have to write a whole lot of other, new code.
I had discussed very similar proposal with @chriseth and @pirapira as implementation of effective ReentranceLock exact one year ago. Instead of ‘TSTORE’ I used ‘CSTORE’ as for context storage (shared context storage for transaction lifetime), but in other aspects it was the similar approach.
The access model for the transaction storage had to remain the same as for permanent storage (a contract should be able to write only into own transient variables of the shared storage). Thus the transaction (or context) storage became very similar to current storage model. Finally Yoichi came with the idea to implement effective ReentrancyLock by setting gas costs to zero if there is no actual storage changes made (this proposal).
I have asked @pirapira & @chriseth about details of our discussion one year ago.
Possibly they will came with details I have already forgot.
Here’s an initial implementation of this EIP for go-ethereum: https://github.com/ethereum/go-ethereum/pull/17208
Note, we’ve hit a few ambiguities, that we resolved in a way that seemed logical, but I’d really like the EIP to be polished up a bit to touch on these issues:
An EVM-global ‘dirty map’ is maintained, tracking all storage slots in all contracts that have been modified in the current transaction.
If the user attempts to set a clean storage slot (arbitrary value) to the existing value, the dirty flag should not be set. This is important, otherwise the user can game the metering system by setting a clean-empty slot to zero and then to a value (e.g. key = 0; key = 5), avoiding the initial 20K gas cost.
When a storage slot is written to for the first time, the slot is marked as dirty.
Please specify that “unless it’s set to the same value”, see above and below for the problematic part.
If the slot was previously set to 0, and is being set to 0, only 200 gas is deducted.
If this operation also sets the dirty flag, the user can game per the above example. If this operation does not set the dirty flag, then we should allow the same feature for any value (i.e. please change to “If the slot was previously set to X, and is being set to X, only 200 gas is deducted.”).
A second important corner case is around dirtiness and revertals. The EIP doesn’t really specify what happens if an inner transaction makes a storage slot dirty, but then reverts. Should the slot be reverted to clean (thus incurring an additional gas hit when modifying it again), or should the slot be kept dirty?
Our code currently reverts the dirtiness too but I guess we can change if needed. However this is an important subtlety to emphasize in the spec.
The spec looks pretty solid (but no implementation yet), but I’d like to ask to maybe rephrase it with simpler sentences. Also try not to refer the same action with different English, e.g. “a storage slot is written to with the value”, “a storage slot’s value is changed”.
Gas netting is not really making SLOAD/SSTORE more versatile, it seems more like a fix. Does either of these two changes have to exclude the other?
I agree your opcodes have an advantage and that is for runtime enforcement of “transience”. Seems it would be hard to do without new opcodes. Seems if this pattern starts being encouraged (even in languages), and actual storage is used, it’s still possible for transient values to remain set after the transaction is over, opening up for some pretty weird bugs.
Btw, chriseth is thinking about adding mutability flags to Solidity, perhaps this could be thought of as as a kind of mutability “flavor” rather then as its own flag?
No, they don’t exclude each other. However, now I think that any new storage/memory that we introduce, should be linear. So if I were to write the transient storage EIP now, I would call it cross-frame memory, and make it behave like a linear memory and not like a non-linear storage. In the hindsight, the contract storage in Ethereum should have been linear too. And I seem to remember that Nick agrees with that too.
Not quite; in my opinion it should be page-table based; that fits much better with how real world memory and disk are organised, and has much lower overhead than maps.
has this EIP been deployed in Ropsten ?
I’ve tested following code, and gas costs seems very strange
Gas cost for function which uses modifier that sets 3 values and then cleans them is around 52800 gas when cost of function that only sets is around 37000 gas
Here is deployed contract on Ropsten
why dummy2() costs more than double of dummy1b()
Hey @adamskrodzki this EIP is not deployed on Ropsten and it is neither deployed on Mainnet. A variant of this (EIP 1283) was supposed to get deployed in the Constantinople fork but due to security concerns it was removed. The EIP 2200 which is planned for Istanbul has another variant which will get deployed soon on Ropsten (from the top of my head the Ropsten hard fork is scheduled at October 4).
Hence the reason that you pay a lot of gas for this cleaning is because you pay the “old” SSTORE cost which in the end nets to 5k SSTORE gas (in case the slot is not zero) which corresponds to what you see on Ropsten - the 52800 transaction is about 15k gas higher (3*5k gas) than the other.
just to confirm,
so no changes about gas mettering of multiple SSTORE to same location where made during Istambul hard fork?
That is quite important information I’'ve missed - latest OpenZeppelin contracts are written like it was in place. For example has reentrencyGuard which works by setting and cleaning some store variable.
They were not deployed during Constantinople but are scheduled for Istanbul which is an upcoming hard fork =)