Lazy-set in FA2-SmartPy
And trying to get back at the blog …
Some people found it surprising, the implementation of FA2-SmartPy contains this in its storage type:
(big_map %operators
(pair (address %owner) (pair (address %operator) (nat %token_id)))
unit))
See for instance the compiled output:
20200910-203659+0000_5060996_mutran_contract.tz#L30-32
or the corresponding source:
multi_asset.py#L224-258
.
The idea is that this is a “lazy set.” It quacks like a set data-structure but it is implemented with a big-map:
(set <VALUE-TYPE>)
→ (big_map <VALUE-TYPE> unit)
- Cons: higher storage and gas costs for sets on the smaller side + some functionality is not available (one cannot iterate on the keys of a big-map).
- Pros: no size limit, hence ensuring that locking down the contract is not possible.
Indeed the issue to avoid is the case where a contract's storage is so big that its deserialization consumes too much gas before even starting the execution of the entrypoint.
The Delphi protocol pushes the limit further than the current protocol but it's still very reachable.
Let's figure out how much …
This is a small contract that fills up a set of consecutive nat
values every
time it is called, it has another unit
entrypoint with minimal gas usage
(You can also
open it in the SmartPy.io editor):
import smartpy as sp
class StoreValue(sp.Contract):
def __init__(self, value):
self.init(store = sp.set(t = sp.TNat))
@sp.entry_point
def add(self, params):
sp.for v in sp.range(sp.len(self.data.store), params.value):
self.data.store.add(v)
@sp.entry_point
def default(self):
pass
if "templates" not in __name__:
@sp.add_test(name = "StoreValue")
def test():
c1 = StoreValue(12)
scenario = sp.test_scenario()
scenario.h1("Store Value")
scenario += c1
scenario += c1.add(value = 15)
Let's experiment with a “manual” Flextesa sandbox …
Originate the contract with an empty set:
tezos-client originate contract c0 transferring 0 from bootacc-0 running setz.tz --init {} --burn-cap 10
Every call adds new elements from the current size of the set to the value of the argument, like this:
tezos-client transfer 0 from bootacc-0 to c0 --arg 60000 --burn-cap 10 --entrypoint add
After some dichotomy, we can see that the limit is at around 59 100. At which point we had enough gas to add a bunch of elements to the set but we put the contract in a state where “doing nothing” fails:
tezos-client transfer 0 from bootacc-0 to c0 --burn-cap 10 --entrypoint default
With Carthage the limit is much lower (I got around 3 100).
In that state, the contract is fully locked-down, in particular any balance cannot be transferred back. This is what the lazy-set is avoiding: a big-map's growth does not impact deserialization gas consumption.
After 8 years of blograstination, this is post #6 of my attempt at not getting too fast lagging behind on the And yes … that's already a long pause between
posts … Quite the slow down on the blogging cadence
right there! I have however done some writing
here and
there
and even some
video-talking.#100DaysToOffload
“challenge” …