EIP-1153: Transient storage opcodes

As mentioned in the other thread, I think this option is less attractive because it introduces more complexity to the EVM, in the form of new opcodes, and another form of memory with new semantics. I think changing gas accounting around SSTORE/SLOAD is much simpler and more versatile.

Further, a change to SSTORE/SLOAD gas accounting will reduce gas costs for contracts already using storage for transient or repeated updates. Examples include contracts using storage to implement locks, and ERC20 tokens using ‘approve’ between contracts. If transient storage is implemented instead, new standards would need to be developed to permit the use of transient storage for these purposes.

When implementing contract-proxies using DELEGATECALL, all direct arguments are relayed from the caller to the callee via the CALLDATA, leaving no room for meta-data between the proxy and the proxee. Also, the proxy must be careful about storage access to avoid collision with target storage-slots. Since transient storage would be shared, it would be possible to use transient storage to pass information between the proxy and the target.

It’s not clear to me why you can’t just pass the metadata along with the call data. Further, this approach would not work if you may end up with recursive calls; it’s the equivalent of using global variables.

Transient storage does not interact with reverts or invalid transactions, that means if a frame reverts, its effects on the transient storage remain until the end of the transaction.

I think this is a really bad idea. Every other state change is reverted when a call reverts or throws; introducing a new semantic for this one type of storage would be counterintuitive, and creates a special case that is likely to lead to a great number of nonobvious bugs and security issues.

I am now trying to see if the Transient Storage is attractive enough without comparison to the SSTORE reduction proposal, which I think stands well on its own.

Thanks for this feedback. You see, I am not very sure how this should work. But for concreteness, I choose non-revertability of the transient storage. It turns out that this non-revertability gives smart contracts a unique resource that wasn’t available before (namely reliable communication from the re-enterancy frames that reverted/threw). But it could have the drawbacks you are talking about. I will try to make this more concrete with some POC.

You can do this with return data. What’s the use-case that you have in mind that you can’t use return/revert data for?

If contract A (frame 1) calls into contract B, then B calls into A again (frame 2). Frame 2 of contract A reverts, and returns some data. Frame of contract B can choose to discard, or modify the return data instead of passing it verbatim to Frame 1 of contract A. Non-revertiable transient storage would allow Frame 2 of contract A pass any info to Frame 1 of contract A regardless of what contract B is doing.

What’s an actual use-case for this, though? Where would it be useful?

In general nonlocal returns are a big source of confusion and nonobvious control flow in traditional programming. I’m not keen on adding them to the EVM.

I do not know yet. I only discovered this two days ago, after having answered @MicahZoltu question above. I decided then not to roll back my choice, but explore it a bit more. Thanks for this discussion, BTW

What is the difference between this use case and existing implementation of ReeantranceLock?
You can set the lock from Frame 2 and read it Frame 1.

Only the necessary lock clean up at the end?

More challenging could be signalling between different frames in different contracts, but I have no good use case yet.

BTW, I can recognize here another dropped proposal I have made in ethereum/solidity
to support an trailing data in message call. Possibly it worth reviewing it once more.

You will need to point me to the existing implementation of ReentranceLock, I could not find it, sorry.

The reentrancy lock has to be unlocked after the call regardless of whether one uses storage or transient storage. Because after the call is complete, the lock is still locked. So no change in usage here, apart from the gas cost.

Transient Storage cannot be used directly to implement signalling between frames of different contracts. All interactions between distinct contracts can only happen via CALL and STATICCALL.

You will need to point me to the existing implementation of ReentranceLock, I could not find it, sorry.

Oh… I mean nothing special. Just a standard implementation with some further message call in the locked scope.
See Contract Mutex, modifier noReentrancy

(Even I am not sure it is good)
why not?

Because of this (from EIP)

you mean definitely: “Only owning contract frames may write- access their transient storage”.
What is bad with read access?

I see what you mean. To read from other contracts’s storage, one would need to modify the TLOAD opcode to have 2 arguments, one for address of account you are reading, and the other - for the address of the “cell” you are reading. It might not be a bad idea, actually.

One use case for contracts reading other contracts transient storage could be calling libraries via CALL (STATICCALL) instead of DELEGATECALL or CALLCODE, and passing structures (like trees and linked lists) without having to serialise them into input data. Calling via CALL and STATICCALL is arguably safer, because you don’t give the callee access to your storage.

I thought about usual public accessors, not about extending TLOAD opcode.

Replacing accessor functions with “native” read access per opcode deserves seperate EIP and cautios evalation. The idea to have a “native” per-reference read-only access to data structures without serialization looks to me as a major change targeting EVM-2.0.
I need to compare it to library pattern.

How much as should the opcode execution cost?
I would try to compare it with gas costs for usual message call.

I suggested 8 gas per TLOAD operation. It does not cost much more to read from other contract’s transient storage. An implementation would most probably already have some kind of nested hash table (contract_address => (storage address => value)) which can be access by 2-argument TLOAD.

Sorry for long delay, @AlexeyAkhunov.

All in all I see two proposals here:

  1. introduce transient storage and TSTORE/TLOAD opcodes.
  2. allow store reading opcodes (TLOAD, SLOAD) to read other contract’s storage.

I think (2) is interesting, because it is much less expensive than usual contract reading calls - we don’t need serialization/deserealization any more. It is some kind of reversing delegate call idea: we let our contract code read external storage.
If we think ethereum as a storage for public data like identities or merkle trees, offered onchain for other contracts, this proposal will help a lot. It allows powerful searches even if they are implemented later in user’s contract instead of in the public storage initially.
Nevertheless, (2) is possible without Transient Storage at all.
In case of permanent storage I am also not quite sure about 8 gas costs. Possibly we should limit an amount of other contract storages accessible by user’s contract, in order to ensure all calls are hitting the cache and not a disk. Then we can keep it low.

For (1) I am still unclear about meaningful distinct usecase.
Technically it means that some contract should create (not load from disk!) a reasonable amount of data (more than few flags for signalling) and offer it for access to other contracts. What use case should it be? I don’t know.

RETURNDATA could have leveraged this generic memory had it existed before, instead of having to put everything in a special memory that is only used for one single thing (and requires 2 instructions). Though most of us are extremely happy that they added it, it can also be argued that return data is “wrong” compared to adding a generic transaction-spanning memory, readable and writable through VM instructions, in a manner similar to what is proposed here.

Languages could easily work around control flow and all other type of issues. Just because they can use it doesn’t mean they have to. But you know all that. Solidity & EVM could just reserve the two first words for return data position and length, for example. The point is there would still only be 2 instructions, TLOAD & TSTORE, just like RETURNDATA & RETURNDATASIZE, but it can be used for a lot more. Return system could be deprecated. If the memory is different arrays mapped to addresses then maybe other transaction-spanning data could be mapped to it as well, for example, maybe CALLDATA could just be assigned to transMem[0x000…000]. It would still be read-only there.

Maybe taking it to far but there seem to be many potential use cases.

Bear in mind that this kind of global transient memory would make static analysis difficult or even impossible in many cases.

1 Like