-
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
Storage refactoring new hope for review #1331
Storage refactoring new hope for review #1331
Conversation
aa358eb
to
3d969bf
Compare
Updated the API to work with the generic key `K: Encode` instead of the old `Key`. Also, the change contains optimization to reduce the size of contracts. In most cases, it is `#[inline(always)]`; but `return_value` also got small optimization; removed usage of `extract_from_slice` where it is not needed.
Removed old 32 bytes `Key`. Replaced it with `u32`. Added `KeyComposer`, it is a helper struct that does all manipulation with the storage key. It allows concat two storage keys into one, compute storage key for fields based on the filed, struct, enum, variants names. Removed all tests and benches. Didn't add it for new primitives because the key is standard `u32` and all keys are calculated during compilation. storage crate: Removed `SpreadLayout`, `PackedLayout`, `SpreadAllocate`, `PackedAllocate`, and all related helper functions. Removed `Packed` struct cause it is the default behavior for storage right now. Added `Lazy` struct that allows `get`/`set` value from/into the storage. It is similar to `Mapping` but works with one storage key and one value. Introduced new main traits to work with storage in `storage/src/traits/storage.rs`. Also added a new `OnCallInitializer` trait to improve the flow with upgradable contracts and support initialization on demand by default. Added `pull_or_init` macro that inits the storage struct if it is impossible to load it. It also can be used as optimization for contracts without an explicit constructor. Replaced implementation of old traits for main primitives with a new one. Added blanket implementation of new traits for structures that are `Packed` by default. It reduces the amount of code and adds support of generic structures but adds problems with tuples(now tuples implement new traits only if inner items are `Packed`). Introduced `AutoKey` and `ManualKey` that allows specifying which key the user wants to use. Added support of it into all traits and structures. Refactored `Mapping` to follow new rules.
Updated storage layout in the metadata. Introduces the concept of roots and leafs. Root defines the storage key for all sub-tree until there will be another Root. Leafs are common types that are part of the sub-tree and serialized/deserialized together into one storage key. Replaced 32 bytes storage key with `u32`. Added validation that all root storage keys don't overlap. Maybe better to add that error or reuse that validator on the `cargo-contract` side to show a more user-friendly error. `RootLayout` and validator are used in codegen(next commit). The contract is wrapped into `RootLayout`, and we do validation for that tree. Metadata now contains name for each struct/enum/variant/field. It is useful information because now the storage key is calculated based on the name of struct/enum/variant/field. storage crate: Added a new helper crate `storage/codegen`. It contains some useful functional that is used in `ink_storage_derive` and in `ink_lang_codegen` crates. It has a method that returns all types of the struct/enum/unit and a method that finds "salt" in the generics of the struct/enum. It uses magic constant "KeyHolder"(about that you can read in issue) to find salt, so I tried to have only one place where we are using that constant. Replaced derive implementation of old trait with new one. You can check the tests to see how the implementation looks like. `Storable` recursively call `encode` and `decode` for all fields. `KeyHolder` return key of the salt. `Item` uses `AutoKey` or key specified by the user. I want to highlight that `PreferredKey` only is used with the `AutoItem` trait. If `PreferredKey` is `AutoKey`, then `AutoItem<AutoGenerated>` select auto-generated key, otherwise preferred. So `AutoItem` trait decides that key to use. It is why derive macro only set `PreferredKey`. Updated drive for `StorageLayout`, now it uses `u32` and pass name of the struct into metadata.
…w the codegen uses `pull_or_init` in the `call`. Updated `execute_constructor` to work with new storage methods. Allowed usage of generics during the declaration of the primary contract storage. Users can specify the default storage key with `KeyHolder` via generic. Added parser for `storage_item` macro with its config.
…object implements `Decode` and `Encode`, it is `Packed`, and it uses the blanket implementation of new traits. In dispatch, codegen started to use a new method to work with storage. In metadata codegen added usage of new `RootLayout`. We wrap the contract into that layout because the contract has its storage key for all inner fields by default. Also added a run of validation logic during metadata generation. Added `storage_item` macro. It transforms all types from autokey into manual key(if types support it). It calculates the storage key based on the name(it uses the `KeyComposer::compute_key` function from the primitives crate). Also, macro generates an additional `Check` structure that includes all raw fields. It helps show correct errors to the user in case of typos, wrong types, etc.
Simplified delegate call example very well causes of new `OnCallInitializer` trait and support of manual specifying key.
Can't highlight something unusual here=)
3d969bf
to
f967986
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.
Yeah this is a lot easier to review!
So far I've only looked at the 1st and 6th commits (and took a peek at the expanded code), but will work through it this week
examples/upgradeable-contracts/delegate-calls/upgradeable-flipper/upgradeable.rs
Show resolved
Hide resolved
let mut scope = self.scoped_buffer(); | ||
let enc_return_value = scope.take_encoded(return_value); | ||
ext::return_value(flags, enc_return_value); | ||
let mut scope = EncodeScope::from(&mut self.buffer[..]); |
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.
Can we not use the append_encoded
and take_appended
methods from ScopedBuffer
instead of using EncodeScope
?
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.
I didn't get your question=) We want to use EncodeScope
because it implements scale::Output
. The optimization is to do nothing with a buffer during result returning(It is the end of the execution, and we don't worry about memory validity after usage).
crates/storage/src/traits/storage.rs
Outdated
|
||
/// Automatically returns the type that should be used for storing the value. | ||
/// | ||
/// Trait is used be codegen to use the right storage type. |
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.
"... used by codegen ..."
What does "correct" mean here?
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.
Updated:
The trait is used by codegen to determine which storage key the type should have.
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.
I think we should find a better name for AutoItem
, something that reflects more what it's about. Maybe StorageType
?
Regarding
The trait is used by codegen to determine which storage key the type should have.
The word type
here is confusing to me. We don't derive the key from the type, no? If I look at fn convert_into_storage_field
the type is not part of the key
.
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.
The word type here is confusing to me. We don't derive the key from the type, no? If I look at fn convert_into_storage_field the type is not part of the key.
The type defines which storage key we should use=)
// Here, the storage key is `123`
let storage: Contract<ManualKey<123>> = Default::default();
// Here, the storage key is `234`
let storage: Contract<ManualKey<234>> = Default::default();
// Here, the storage key is a hash of `234` and `123`
let storage: Contract<ManualKey<234, ManualKey<123> /* It is Salt or ParentStorageKey */ >> = Default::default();
I think we should find a better name for AutoItem, something that reflects more what it's about. Maybe StorageType?
StorageType
is the previous name of the Item
=) And previously, it was AutoStorageType
. I'm okay with StorageType
but it doesn't highlight that it is related to automatically type determination. Item
helps to determine the type too, but it also contains PreferredKey
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.
What do you think about?:
pub trait Item<AutoGenerated: StorageKey> {
type Type: Storable;
type PreferredKey: StorageKey;
}
pub trait StorageType<AutoGenerated: StorageKey> {
type Type: Storable;
}
or maybe simplify to Generated
bytes[offset + 2], | ||
bytes[offset + 3] | ||
)?; | ||
use ink_prelude::vec; |
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.
We should still have some tests here imo
pub trait PullOrInitFallback<T: Storable> { | ||
#[allow(dead_code)] | ||
fn pull_or_init(key: &Key) -> T { | ||
pull_storage(key) | ||
} | ||
} | ||
impl<T: Storable> PullOrInitFallback<T> for PullOrInit<T> {} |
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.
Is this trait really necessary? Can you not implement this on PullOrInit
directly? It looks like in the codegen you go through the PullOrInit
struct anyways
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.
That trait provides a default implementation of the pull_or_init
method if the contract doesn't implement the OnCallInitializer
trait. The same if don't for is_result_type
(IsResultTypeFallback
).
It is a way how to get autoref specialization without specialization feature)
/// Pulls the struct from the storage or creates and new one and inits it. | ||
#[macro_export] | ||
#[doc(hidden)] | ||
macro_rules! pull_or_init { | ||
( $T:ty, $key:expr $(,)? ) => {{ | ||
#[allow(unused_imports)] | ||
use $crate::traits::pull_or_init::PullOrInitFallback as _; | ||
|
||
$crate::traits::pull_or_init::PullOrInit::<$T>::pull_or_init(&$key) | ||
}}; | ||
} |
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.
I don't think this macro is really necessary. It's only used once it the codegen, and a couple of times in the tests
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.
It is a separate macro with its module because of tests. But tests check that everything works as expected=)
crates/storage/src/lazy/mapping.rs
Outdated
/// } | ||
/// | ||
/// impl MyContract { | ||
/// #[ink(constructor)] | ||
/// pub fn new() -> Self { | ||
/// ink_lang::utils::initialize_contract(Self::new_init) | ||
/// let mut instance = Self::default(); | ||
/// instance.new_init(); |
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.
Similar thing here, I'd take the code in new_init
and just fold it here. But again, if we just want to show how to use ManualKey
do we even need the rest of the example?
crates/storage/src/lazy/mapping.rs
Outdated
@@ -154,7 +166,8 @@ where | |||
where | |||
Q: scale::EncodeLike<K>, | |||
{ | |||
pull_packed_root_opt(&self.storage_key(&key)) | |||
ink_env::get_contract_storage::<(&Key, Q), V>(&(&KeyType::KEY, key)) | |||
.unwrap_or_else(|error| panic!("failed to get value in mapping: {:?}", error)) |
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.
.unwrap_or_else(|error| panic!("failed to get value in mapping: {:?}", error)) | |
.unwrap_or_else(|error| panic!("Failed to get value in Mapping: {:?}", error)) |
I wonder if we can test this somehow. Maybe by clearing the underlying contract storage manually and then trying to pull. Really this shouldn't happen unless a storage migration messes up your storage layout
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.
Thank you @HCastano for the review=) I hope I didn't miss any of your comments.
I applied most of your suggestions locally, updated/added comments and tests. There are several major questions regarding naming let's discuss them and after I will push all changes.
-
Hernando suggested renaming the
KeyHolder
trait into theStorageKey
trait. During the previous review iteration, we decided to use the namingKeyHolder
and we tried to remove all "Storage" prefixes for all entities in theink_storage
. TheKeyHolder
trait means that the type occupies some storage key. SoStorageKey
also fits well. -
The trait
Item<X: KeyHolder>
is used to manipulate the type's storage key. By default, the type will use the storage key passed asX
. I called itSalt
because in the implementation underhood it is used as a salt to generate the final storage key. But it is not accurate naming for public trait and may be better to name it "Key: StorageKey" or "KeyHolder: StorageKey", "ParentKey: StorageKey" or maybe another name(remember we can select a good name for bound too). -
Packed
vs non-Packed
: With the introduction of blanket implementation of thePacked
trait, it simplified the implementation ofStorable
and other traits for primitives. We can simplify the previous structure as Hernando suggested. I used the old structure because we didn't decide if we wanted to have two macros,ink::packed
andink::non_packed
. With two macros, maybe we don't want to have blanket implementation. Do we want to discuss it or are we okay with the current implementation and I will simplify the structure more? -
All functions from
ink_env
usescale::Decode
andscale::Encode
.ink_env
doesn't know aboutink_storage::traits::Storable
. So here I createdEncodeWrapper
andDecodeWrapper
to transformStorable
intoscale::Encode
orscale::Decode
. And it is a hack=) I tried not to break the abstraction that all traits related to storage in theink_storage
crate. What do you think, can we move theStorable
definition toink_env
and useStorable
forink_env::set_contract_storage
andget_contract_storage
instead ofscale::Decode
andscale::Encode
? And where we should define derive macro forStorable
in that case?
let mut scope = self.scoped_buffer(); | ||
let enc_return_value = scope.take_encoded(return_value); | ||
ext::return_value(flags, enc_return_value); | ||
let mut scope = EncodeScope::from(&mut self.buffer[..]); |
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.
I didn't get your question=) We want to use EncodeScope
because it implements scale::Output
. The optimization is to do nothing with a buffer during result returning(It is the end of the execution, and we don't worry about memory validity after usage).
crates/storage/src/traits/storage.rs
Outdated
/// | ||
/// The trait is automatically implemented for [`Packed`](crate::traits::Packed) types | ||
/// via blank implementation. | ||
pub trait Item<Salt: KeyHolder> { |
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.
It was initially called Salt
because it is only one part of the storage key, and in most cases, it is salt for the hash function=)
But Key
or KeyHolder
for the generic sounds better. Let's discuss about it in general
crates/storage/src/traits/storage.rs
Outdated
|
||
/// Automatically returns the type that should be used for storing the value. | ||
/// | ||
/// Trait is used be codegen to use the right storage type. |
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.
Updated:
The trait is used by codegen to determine which storage key the type should have.
@@ -10,7 +10,7 @@ publish = false | |||
[dependencies] | |||
ink_primitives = { path = "../../../../crates/primitives", default-features = false } | |||
ink_metadata = { path = "../../../../crates/metadata", default-features = false, features = ["derive"], optional = true } | |||
ink_env = { path = "../../../../crates/env", default-features = false, features = ["ink-debug"] } |
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.
I think this feature removal is unintentional.
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.
I removed it to track the actual size of the contract
I don't mind having the Storage prefix in this case. It makes things clearer imo.
So a big part of the confusion for me is that the It's not used on I think there's room for simplification around the traits and implementations
I'd stick with the current implementation. We can always have a follow up PR if we decide there
Haven't gotten around to this yet, I'll need to think about it. |
# Conflicts: # crates/lang/macro/Cargo.toml
Removed `delegate-call` example with `OnCallInitializer`
…ork-new-hope # Conflicts: # .gitlab-ci.yml
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!
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.
Green, thanks so much for this 💚! It's definitely been quite an effort on both sides, and
it's about time we get this into the hands of users. There's inevitably gonna be
follow up PRs as we discover bugs, edge cases, and quirks. Hopefully we catch
all the major ones before this hits a mainnet deployment 😅
Green, can you put down either a Polkadot or Kusama address in the PR description so
we can submit a tip for you?
@xgreenx As Hernando wrote above: we want to give you a tip for the huge amount of work you've put into this PR (and the previous one from which this one was created + the smaller ones which were split off). In total you worked on this project for over six months, starting with the discussions in #1134, specifying it out. And then over four months ago you created the first PR and met with a number of people from the core team in person two times to discuss it. Thank you so much! We're very happy to have you be part of our ecosystem! Could you edit your description of this PR and add either one of those?
or
|
Yea, we finally did it!=)
I hope the number of problems will be as few as possible=) But definitely, we will have follow-up PRs to improve the feature, documentation, etc.
💚💚💚 |
/tip large |
@cmichi A large tip was successfully submitted for xgreenx (1nNaTpU9GHFvF7ZrSMu2CudQjXftR8Aqx58oMDgcuoH8dKe on polkadot). https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frpc.polkadot.io#/treasury/tips |
* Import new unstable functions with transparent hashing. Updated the API to work with the generic key `K: Encode` instead of the old `Key`. Also, the change contains optimization to reduce the size of contracts. In most cases, it is `#[inline(always)]`; but `return_value` also got small optimization; removed usage of `extract_from_slice` where it is not needed. * primitives crate: Removed old 32 bytes `Key`. Replaced it with `u32`. Added `KeyComposer`, it is a helper struct that does all manipulation with the storage key. It allows concat two storage keys into one, compute storage key for fields based on the filed, struct, enum, variants names. Removed all tests and benches. Didn't add it for new primitives because the key is standard `u32` and all keys are calculated during compilation. storage crate: Removed `SpreadLayout`, `PackedLayout`, `SpreadAllocate`, `PackedAllocate`, and all related helper functions. Removed `Packed` struct cause it is the default behavior for storage right now. Added `Lazy` struct that allows `get`/`set` value from/into the storage. It is similar to `Mapping` but works with one storage key and one value. Introduced new main traits to work with storage in `storage/src/traits/storage.rs`. Also added a new `OnCallInitializer` trait to improve the flow with upgradable contracts and support initialization on demand by default. Added `pull_or_init` macro that inits the storage struct if it is impossible to load it. It also can be used as optimization for contracts without an explicit constructor. Replaced implementation of old traits for main primitives with a new one. Added blanket implementation of new traits for structures that are `Packed` by default. It reduces the amount of code and adds support of generic structures but adds problems with tuples(now tuples implement new traits only if inner items are `Packed`). Introduced `AutoKey` and `ManualKey` that allows specifying which key the user wants to use. Added support of it into all traits and structures. Refactored `Mapping` to follow new rules. * metadata crate: Updated storage layout in the metadata. Introduces the concept of roots and leafs. Root defines the storage key for all sub-tree until there will be another Root. Leafs are common types that are part of the sub-tree and serialized/deserialized together into one storage key. Replaced 32 bytes storage key with `u32`. Added validation that all root storage keys don't overlap. Maybe better to add that error or reuse that validator on the `cargo-contract` side to show a more user-friendly error. `RootLayout` and validator are used in codegen(next commit). The contract is wrapped into `RootLayout`, and we do validation for that tree. Metadata now contains name for each struct/enum/variant/field. It is useful information because now the storage key is calculated based on the name of struct/enum/variant/field. storage crate: Added a new helper crate `storage/codegen`. It contains some useful functional that is used in `ink_storage_derive` and in `ink_lang_codegen` crates. It has a method that returns all types of the struct/enum/unit and a method that finds "salt" in the generics of the struct/enum. It uses magic constant "KeyHolder"(about that you can read in issue) to find salt, so I tried to have only one place where we are using that constant. Replaced derive implementation of old trait with new one. You can check the tests to see how the implementation looks like. `Storable` recursively call `encode` and `decode` for all fields. `KeyHolder` return key of the salt. `Item` uses `AutoKey` or key specified by the user. I want to highlight that `PreferredKey` only is used with the `AutoItem` trait. If `PreferredKey` is `AutoKey`, then `AutoItem<AutoGenerated>` select auto-generated key, otherwise preferred. So `AutoItem` trait decides that key to use. It is why derive macro only set `PreferredKey`. Updated drive for `StorageLayout`, now it uses `u32` and pass name of the struct into metadata. * Removed `initialize_contract` and related to initialization stuff. Now the codegen uses `pull_or_init` in the `call`. Updated `execute_constructor` to work with new storage methods. Allowed usage of generics during the declaration of the primary contract storage. Users can specify the default storage key with `KeyHolder` via generic. Added parser for `storage_item` macro with its config. * Removed the old codegen related to spread and packed layout. If some object implements `Decode` and `Encode`, it is `Packed`, and it uses the blanket implementation of new traits. In dispatch, codegen started to use a new method to work with storage. In metadata codegen added usage of new `RootLayout`. We wrap the contract into that layout because the contract has its storage key for all inner fields by default. Also added a run of validation logic during metadata generation. Added `storage_item` macro. It transforms all types from autokey into manual key(if types support it). It calculates the storage key based on the name(it uses the `KeyComposer::compute_key` function from the primitives crate). Also, macro generates an additional `Check` structure that includes all raw fields. It helps show correct errors to the user in case of typos, wrong types, etc. * Updated all examples to use a new API. Simplified delegate call example very well causes of new `OnCallInitializer` trait and support of manual specifying key. * UI tests for a new codegen. Can't highlight something unusual here=) * Apply all suggestion from the review * Make CI happy * Fix tests * Fix tests * Fix tests * Fix tests * Apply suggestions: - In most cases it is comments=) - Moved `pull_or_init` on one level upper. - Put the tests into the `impls/mod.rs` * Fix doc * Add comment to autoref specialisation * Suggestion from the review * Revert back u8 * Remove unwrap * Collapse if let * Fixed overflow for enums * Fixing comments * Renamed `Item` to `StorableHint` and `AutoItem` to `AutoStorableHint` * Fix test * Renamed key_holder. Add UI test for double storage_item. Applied suggestion from the review. * Nightly fmt * Remove `Packed` path * Fix doc test * Apply suggestions from hte review * Fixed build * Fix build * Removed `initialize_contract` from linting and deleted all tests * Fix doc link * Fix mapping example * Applied suggestion. Removed `delegate-call` example with `OnCallInitializer` * Removed `delegate-calls` from the CI. Replaced it with `set-code-hash` * fix test * fix test * Fix CI to use stable for contract build * Fix CI to use stable for examples
We decided to abandon old PR because it contains a lot of commits and comments and is hard to review.
The whole change was split into seven commits with their descriptions. In most cases, it is crate-based splitting. But some crates united together because they related to the same area.
You could check first the commit six with updated examples. It will help to understand how the usage changed. But after better to go in order of commits.
Commits:
First commit imports new unstable functions with transparent hashing.
Updated the API to work with the generic key
K: Encode
instead of the oldKey
.Also, the change contains optimization to reduce the size of contracts. In most cases, it is
#[inline(always)]
; butreturn_value
also got small optimization; removed usage ofextract_from_slice
where it is not needed.Second commit:
primitives crate:
Removed old 32 bytes
Key
. Replaced it withu32
. AddedKeyComposer
, it is a helper struct that does all manipulation with the storage key. It allows concat two storage keys into one, compute storage key for fields based on the filed, struct, enum, variants names.Removed all tests and benches. Didn't add it for new primitives because the key is standard
u32
, and all keys are calculated during compilation.storage crate:
Removed
SpreadLayout
,PackedLayout
,SpreadAllocate
,PackedAllocate
, and all related helper functions.Removed
Packed
struct cause it is the default behavior for storage right now. AddedLazy
struct that allowsget
/set
value from/into the storage. It is similar toMapping
but works with one storage key and one value.Introduced new main traits to work with storage in
storage/src/traits/storage.rs
.Also added a new
OnCallInitializer
trait to improve the flow with upgradable contracts and support initialization on demand by default. Addedpull_or_init
macro that inits the storage struct if it is impossible to load it. It also can be used as optimization for contracts without an explicit constructor.Replaced implementation of old traits for main primitives with a new one. Added blanket implementation of new traits for structures that are
Packed
by default. It reduces the amount of code and adds support of generic structures but adds problems with tuples(now tuples implement new traits only if inner items arePacked
).Introduced
AutoKey
andManualKey
that allows specifying which key the user wants to use. Added support of it into all traits and structures.Refactored
Mapping
to follow new rules.Third commit:
metadata crate:
Updated storage layout in the metadata. Introduces the concept of roots and leafs. Root defines the storage key for all sub-tree until there is another Root. Leafs are common types that are part of the sub-tree and serialized/deserialized together into one storage key.
Replaced 32 bytes storage key with
u32
.Added validation that all root storage keys don't overlap. Maybe better to add that error or reuse that validator on the
cargo-contract
side to show a more user-friendly error.RootLayout
and validator are used in codegen(next commit). The contract is wrapped intoRootLayout
, and we do validation for that tree.Metadata now contains name for each struct/enum/variant/field. It is useful information because now the storage key is calculated based on the name of struct/enum/variant/field.
storage crate:
Added a new helper crate
storage/codegen
. It contains some useful functional that is used inink_storage_derive
and inink_lang_codegen
crates. It has a method that returns all types of the struct/enum/unit and a method that finds "salt" in the generics of the struct/enum. It uses the magic constant "KeyHolder"(about that you can read in the issue) to find salt, so I tried to have only one place where we are using that constant.storage derive crate:
Replaced derive implementation of old trait with a new one. You can check the tests to see what the implementation looks like.
Storable
recursively callsencode
anddecode
for all fields.KeyHolder
return the key of the salt.Item
usesAutoKey
or a key specified by the user. I want to highlight thatPreferredKey
only is used with theAutoItem
trait. IfPreferredKey
isAutoKey
, thenAutoItem<AutoGenerated>
select auto-generated key, otherwise preferred. SoAutoItem
trait decides which key to use. It is why derive macro only setPreferredKey
.Updated drive for
StorageLayout
, now it usesu32
and passes the name of the struct into metadata.Fourth commit removed initialize_contract and related to initialization stuff. Now the codegen uses
pull_or_init
in thecall
. Updatedexecute_constructor
to work with new storage methods.Allowed usage of generics during the declaration of the primary contract storage. Users can specify the default storage key with
KeyHolder
via generic.Added parser for
storage_item
macro with its config.Fifth commit removed the old codegen related to spread and packed layout. If some object implements
Decode
andEncode
, it isPacked
, and it uses the blanket implementation of new traits.In dispatch, codegen started to use a new method to work with storage.
In metadata codegen added usage of new
RootLayout
. We wrap the contract into that layout because the contract has its storage key for all inner fields by default. Also added a run of validation logic during metadata generation.Added
storage_item
macro. It transforms all types from autokey into manual key(if types support it). It calculates the storage key based on the name(it uses theKeyComposer::compute_key
function from the primitives crate). Also, macro generates an additionalCheck
structure that includes all raw fields. It helps show the user correct errors in typos, wrong types, etc.Sixth commit updates examples to sue a new API.
Seventh commit adds new UI tests and fixes the old one.