Store contract state offchain in a trustless manner

Consider a system with a storage requirement too expensive to execute onchain.
For example, updating 500 storage slots can cost more than 10 million gas units.
One solution for this is to somehow store the information offchain in a trustless manner.

I have implemented a base contract (to be derived from), which supports this L2-like solution.
I am not sure whether or not something like this has been considered and/or formally published before.
So I would like to request some review/feedback of the idea itself, as well as the actual contract:

Thank you :slight_smile:

1 Like

Thanks for sharing!

This approach is interesting to evaluate for some applications but scalability is bad in a couple of ways.

First, as you mention in the readme the cost of validating the state grows with the size of the state. In fact, the cost doesn't scale linearly with state size, but quadratically. The reason is that you copy the state to memory in order to hash it (among other things), and the cost of writing to memory grows quadratically with memory size.

The second way in which this hurts scalability is by contention on modifications to the state. Because users need to provide the current state in order to modify it, you will run into issues with multiple transactions in the mempool. Without any special measures in place, a contract would be reduced to a single update per block. You could improve on this but it sounds difficult.

A similar approach that would improve on one if not both points is a dynamic merkle tree. It helps with the first point by having cost that is logarithmic on the state size, and on the second by maybe allowing a write operation to not contend with concurrent read operations, but I think that last part would only work for some applications. We've explored this before in the pull request below but we don't have any active plans to actually merge this work.

Thank you very much for the detailed review!

A couple of notes:

  1. I believe that it does in fact scale linearly, i.e., the runtime gas cost is O(n) with respect to the size of the data (quadratically, which is x4, still falls under linear growth).

  2. No doubt it is subjected to contention, as you've described it. On the one hand, it ensures 100% protection from front-running, which is actually beneficial. On the other hand, user transactions will become very susceptible to reverting, essentially making the system "extremely single threaded". Normally, there are better ways to handle front-running to sufficient degree, for example, the minReturn parameter used in conventional AMM trading functions. Here, it is most certainly not the case, which means that the entire system can be DDOSed if the incentive for that exists. That being said, I don't think that it is necessarily limited to a single transaction per block as you claim.

I overall agree that it is not a very useful approach in general.

As stated in the README file, it is mostly suitable for a rather small set of system types, namely, systems which require updating a large number of storage slots in a single transaction, but do not require maintaining a much larger number of storage slots altogether.

Thanks :slight_smile:

By quadratically I mean O(n^2)!