-
Notifications
You must be signed in to change notification settings - Fork 432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement storage (revision 2) module #311
Conversation
6d24957
to
0f7b0e1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WIP review (8/14 files).
The ToDo comments have been moved to the associated PR description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
69/69 🌮
All in all LGTM.
There is a fair amount of "similar" code in collections, which makes sense but we must remember if we apply any bugfixes/optimizations to do so in other collections where there is a similar code block.
Co-authored-by: Andrew Jones <[email protected]>
Co-authored-by: Andrew Jones <[email protected]>
Co-authored-by: Andrew Jones <[email protected]>
Also I would suggest another pass from the |
Co-authored-by: Andrew Jones <[email protected]>
Co-authored-by: Andrew Jones <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Co-authored-by: Andrew Jones <[email protected]>
Co-authored-by: Andrew Jones <[email protected]>
…redo-init-and-flush
Quick question here since it's merged but also mentioned in the v3.0 docs issue - is this going in already, or will it be part of the 3.0 release only? |
Yes, this PR resembles parts of the upcoming ink! 3.0. |
New Contract Initialization & Finalization
Technical ToDo List
SpreadLayout
&PackedLayout
impls of Rust primitives.expect
for some iterator impls of storage collections.storage::Pack
instorage2::stash::Entry
internally.storage::Pack
in the internals of data structures and disallow typesT
that cannot be put into such astorage::Pack
. Users can opt-into usingstorage2::Box
to be able to pack otherwise non-package types.storage2::Bitvec
: required for dynamic storage allocator.Makestorage2::Stash
generic over a symbol type.storage2::HashMap
.storage2::collections::HashMap
.storage2::collections::Bitvec
.SpreadLayout
unit tests for storage collections:storage::Vec
storage::SmallVec
storage::Stash
storage::HashMap
storage::Box
storage::Bitvec
storage::Pack
storage::Memory
storage::Box
lazy
abstractions.LazyCell
LazyArray
LazyIndexMap
LazyHashMap
PackedLayout
elementsVec
SmallVec
Stash
- still usesPack<Entry<T>>
internally instead ofEntry<T>
HashMap
- still usesPack<ValueEntry<T>>
internally instead ofValueEntry<T>
Bitvec
- still usesPack<Bits256>
instead ofBits256
internallyDrop
forstorage
data structures to clean up their associated storage.Box
Vec
Stash
SmallVec
HashMap
clear_packed_at
implementations of some lazy data structures.storage2::LazyArray
storage2::LazyIndexMap
storage2::LazyHashMap
Follow-up Work Items
LazyIndexMap
andLazyHashMap
.SpreadLayout::pull_spread
and utility functions such aspull_spread_root
returnResult
instead of panicking.storage::HashMap
entry APIink_lang
#[ink(constructor)]
syntax and semantics to adjust to the changes.#[ink(constructor)]
syntax and semanticsstorage
module and replace all usage by the newstorage2
module.storage2::collections::boxed
intostorage2::alloc
.storage2::Stash::take_drop
.storage2::Stash::take_drop
instorage2::HashMap::remove
storage
abstractionTrie
that uses storage subtries to pack storage entities.storage
data structures:VecDeque
: Storage data structure with an API similar to Rust'sVecDeque
.BTreeMap
: Re-implementation of oldstorage::BTreeMap
.BinaryHeap
: Re-implementation of oldstorage::BinaryHeap
.PushOnDrop
abstractionPushOnDrop
for dynamic storage allocator.storage2::Memory<T>
to specify entities that are never synchronized with the contract storage. This is useful for intermediate state between messages.Debug
impls that can properly handle theUnsafeCell
field and displays the cache below.lazy::Entry
, e.g. some methods should be&self
instead of&mut self
etc.storage::HashMap::to_offset_key
key calculation.DynamicAllocator
for storage into a "generic" data structure usable by other ink! abstractions.BitStash
maybe?u8
masks which should be much more efficient.storage::Vec
clear(&mut self)
: Empties the storage vector. More efficient than doing it manually. Might be useful for small storage vector instances.set(&mut self, index: Index, new_value: T)
: Sets the value of an indexed elements without loading and returning the old value which is more efficient. There currently is no alternative to that.SpreadLayout
PackedLayout
storage::Key
to be based onu128
instead of[u8; 32]
.Why?
Currently ink! has some major problems around contract initialization (contract instantiation), contract setup upon a call and finalization (contract tear-down after instantiation or call).
Contract instantiation and the setup phase upon calling a contract are treated the same even though they do completely different tasks and also have different assumptions about the state of the contract storage.
Due to this major problem of unifying two concepts that should not have been unified many abstractions in ink! have developed in an incorrect way leading to bad UX and non optimal performance and memory usage characteristics.
New Design
This PR changes the way how contract instantiation and contract call setup phase are handled and adjusts the contract finalization.
So far
#[ink(constructor)]
messages always had to be&mut self
and return nothing as if they were constructors of known object oriented languages. However, Rust constructors normally returnSelf
and don't have a receiver. This design separated ink! from Rust quite a bit and made it awkward for Rustaceans to "unlearn" constructors.The new design will treat ink! constructors just like Rust constructors. This has the consequence that you can no longer access the environment via
self
because there is no longer aself
receiver in an#[ink(constructor)]
. For that purpose we introduce another universal way of accessing the environment from within#[ink(constructor)]
and#[ink(message)]
functions viaSelf::env()
.Example: ERC20 Constructor
OLD
NEW
New Storage Primitives
So far ink! storage has been mainly managed by the following primitives:
storage::SyncCell
: Low-level primitive to load and store a single cached cell of typeT
.storage::SyncChunk
: Low-level primitive to load and store a chunk of cached and cells of typeT
.storage::Value
: High-level primitive to manage a singleT
.storage::Vec
: High-level primitive to manage a vector ofT
.And other storage collections similar to
storage::Vec
that manage a whole set of storage entities each living in their own cell.All of these primitives mainly have a single or multiple
ink_primitives::Key
instances to maintain a mapping to some concrete contract storage entries all the time. This was designed to allow toflush
the contract storage at any point during contract execution which could be a valuable counter measurement against re-entrancy attacks upon calling another contract. However, this whole mechanic of enforcing all storage types to always have direct pointers into their mapped storage region has significant drawbacks. For once we cannot support Rust primitive types since they themselves cannot hold such a key.The new storage abstraction data structures would no longer have their respective contract storage region encoded into themselves. Instead they rely on contract initialization and finalization routines to provide them with the correct mapping. So those mappings are now managed by the routines instead of by the data structures.
Furthermore this means that some already existing storage abstractions need adjustments, others might even be removed.
New Storage Types
The new storage types introduced by this PR design are the following:
storage::Lazy<T>
storage::Pack<T>
T
into using just a single cell instead of distributing to as many cells as possible.storage::Box<T>
T
on the storage.storage::LazyMap<K, V>
K
instead of an index.Removed or deprecated storage types are:
storage::SynCell<T>
storage::Lazy<T>
storage::SynChunk<T>
storage::LazyMap<T>
storage::Value<T>
T
, either useT
orstorage::Lazy<T>
The other
storage
collections will remain mostly the same but are having adjusted interfaces to meet the new requirements. E.g. they will have proper constructors, e.g.storage::Vec::new()
and implement the new storage traits.Example Use Cases
You simply want to store and load a single
i32
.i32
as type. It won't be lazily loaded but that wasn't a requirement.You want to have a lazily loadable
i64
value.storage::Lazy<i64>
. It will load lazily but besides that behave the same as ai64
.Note that
storage
collections that manage multiple elements are always lazy.Lazy loading storage primitives necessarily need to have a key somehow to lazily load from when needed.
Lazy
The structure of
storage::Lazy<T>
is roughly this:However, due to the fact that we want to access the
T
of a lazy by reference and that the loading of aLazy<T>
should require a&self
receiver instead of a&mut self
receiver we need a few low-level adjustments to let that happen. Fore more details the reader should look into the source of the PR.