-
Notifications
You must be signed in to change notification settings - Fork 71
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
Update storage docs for ink! v4 #137
Merged
Merged
Changes from 76 commits
Commits
Show all changes
78 commits
Select commit
Hold shift + click to select a range
33cbdf7
WIP
xermicus bac0ba0
eager loading best explained in storage layout section
xermicus fc0a0b9
rename to storage-layout
xermicus e83dcc3
WIP update mappings
xermicus daf100a
update mappings
xermicus a985a0b
WIP storage layout
xermicus bbf7610
explain packed layout
xermicus ee9fa22
WIP
xermicus 1ef577b
explain lazy
xermicus 270f826
update example
xermicus 07398fb
WIP custom storage types
xermicus 16d86db
custom storage types
xermicus 037c35b
WIP metadata storage docs
xermicus 009a0d1
update storage layout in metadata
xermicus fc05550
typos
xermicus c873ff0
Update versioned_docs/version-4.0.0-alpha.1/datastructures/custom.md
xermicus d39bf54
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus a2d4880
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus c5ee9db
Update versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
xermicus 14d4b8a
Update versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
xermicus d23196d
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus 26ffbb3
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus b75865e
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus 601cb69
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 278b18d
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 37dd49a
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 855c005
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 3889490
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus 55ca0a9
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus 0d06b29
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus 3be2041
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus de68341
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus ed67c09
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 6a86095
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 3282161
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus a551e16
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 8dbce95
rework mapping overview
xermicus 055a411
rework lazy overview
xermicus 5685d84
add mapping example for local variable
xermicus 76bc8ae
mapping storage access example
xermicus 852607a
explain why iteration over mapping might be expensive
xermicus d1bf3a8
small fixes
xermicus 9832d47
more fixes
xermicus 54954a5
wip transparent hashing
xermicus e19ead9
fix layout of storage in metadata section
xermicus 69544a3
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus a4edead
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus 125b4e0
Update versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
xermicus de865b3
Update versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
xermicus 475082c
smol fix
xermicus 0c59044
explain mapping loading behavior first
xermicus 9448a2e
mention other datastructures under collections prelude
xermicus 7c0826f
mention that the ink mapping can store a lot of values
xermicus ad310b3
mention pitfall of the contract trapping when decoding values too large
xermicus 213762d
refer to ink::prelude::vec::Vec
xermicus c61c021
Update versioned_docs/version-4.0.0-alpha.1/datastructures/custom.md
xermicus 9b07b1f
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus f8fca53
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 8f6fbe4
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus 9464011
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus 78a0e29
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus 4cdf361
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus a5998c4
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus 12f2884
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus f6fb885
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus 3447c94
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus f1d2eac
Merge branch 'master' into cl/storage-docs
xermicus 53acaea
Apply suggestions from code review
xermicus e02fab3
impl nandos comments
xermicus ac6d467
impl greens comments
xermicus 3418267
fix oopise
xermicus 8e0891d
link ink::storage_item macro docs
xermicus 6942184
Fix some typos
HCastano 04fd497
Couple of small nits
HCastano e22dab2
Fix some typos
HCastano ce6bb9f
Remove trailing whitespaces for entire PR
HCastano 0eb238a
Merge branch 'master' into cl/storage-docs
xermicus 5481b4d
better explain storage_item macro
xermicus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,48 +3,125 @@ title: Custom Data Structures | |
slug: /datastructures/custom-datastructure | ||
--- | ||
|
||
:::caution | ||
TODO | ||
The `ink_storage` crate provides useful utilities and data structures to organize and | ||
manipulate the contract's storage. However, contract authors should know that they can | ||
also create their own custom data structures. | ||
|
||
Beware, this page is no longer up to date for 4.0! | ||
::: | ||
## Using custom types on storage | ||
Any custom type wanting to be compatible with ink! storage must implement the | ||
[`Storable`](https://docs.rs/ink_storage_traits/4.0.0-beta/ink_storage_traits/trait.Storable.html) | ||
trait, so it can be SCALE | ||
[`encoded`](https://docs.rs/parity-scale-codec/3.2.2/parity_scale_codec/trait.Encode.html) | ||
and | ||
[`decoded`](https://docs.rs/parity-scale-codec/3.2.2/parity_scale_codec/trait.Decode.html). | ||
Additionally, the traits | ||
[`StorageLayout`](https://docs.rs/ink_storage/latest/ink_storage/traits/trait.StorageLayout.html) | ||
and [`TypeInfo`](https://docs.rs/scale-info/2.3.1/scale_info/trait.TypeInfo.html) | ||
are required as well. But don't worry, usually these traits can just be derived: | ||
|
||
```rust | ||
/// A custom type that we can use in our contract storage | ||
#[derive(scale::Decode, scale::Encode)] | ||
#[cfg_attr( | ||
feature = "std", | ||
derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) | ||
)] | ||
pub struct Inner { | ||
value: bool, | ||
} | ||
|
||
While the `ink_storage` crate provides tons of useful utilities and data structures to organize and manipulate the contract's storage contract authors are not limited by its capabilities. By implementing the core `SpreadLayout`/`PackedLayout` traits (and the `StorageLayout` trait for supporting the metadata generated for the `.contract` bundle) users are able to define their very own custom storage data structures with their own set of requirement and features that work along the `ink_storage` data structures as long as they fulfill the mere requirements stated by those two traits. | ||
#[ink(storage)] | ||
pub struct ContractStorage { | ||
inner: Inner, | ||
} | ||
``` | ||
|
||
A basic example of a custom struct is shown below: | ||
Even better: there is a macro | ||
[`#[ink::storage_item`](https://docs.rs/ink_macro/4.0.0-beta.1/ink_macro/attr.storage_item.html), | ||
which derives all necessary traits for you. If there is no need to implement any special | ||
behaviour, the above code example can be simplified further as follows: | ||
|
||
``` rust | ||
struct Inner { | ||
value: bool | ||
```rust | ||
/// A custom type that we can use in our contract storage | ||
#[ink::storage_item] | ||
pub struct Inner { | ||
value: bool, | ||
} | ||
|
||
#[ink(storage)] | ||
pub struct MyContract { | ||
inner: Inner | ||
pub struct ContractStorage { | ||
inner: Inner, | ||
} | ||
``` | ||
|
||
Compiling the above will result in errors. While having an inner struct which holds only a boolean might not be the best idea, it serves well to illustrate how to implement the trait: | ||
Naturally, you can as well implement any required trait manually. Please directly refer to | ||
the relevant trait documentations for more information. | ||
|
||
## Generic storage fields | ||
|
||
It is possible to use generic data types in your storage, as long as any generic type | ||
satisfies the required storage trait bounds. In fact, we already witnessed this in the | ||
previous sections about the | ||
[`Mapping`](https://docs.rs/ink_storage/4.0.0-beta.1/ink_storage/struct.Mapping.html). | ||
|
||
Let's say you want a mapping where accessing a non-existent key should just return | ||
it's default value, akin to how mappings work in Solidity. Additionally, you want to know | ||
how many values there are in the mapping (its length). This could be implemented as a | ||
thin wrapper around the ink! `Mapping` as follows: | ||
|
||
```rust | ||
/// Values for this map need to implement the `Default` trait. | ||
/// Naturally, they also must be compatible with contract storage. | ||
/// Note that the underlying `Mapping` type only supports `Packed` values. | ||
#[ink::storage_item] | ||
pub struct DefaultMap<K, V: Packed + Default> { | ||
values: Mapping<K, V>, | ||
length: u32, | ||
} | ||
Comment on lines
+87
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this example, very nice=) |
||
|
||
``` rust | ||
impl SpreadLayout for Inner { | ||
const FOOTPRINT: u64 = 1; | ||
impl<K: Encode, V: Packed + Default> DefaultMap<K, V> { | ||
/// Accessing non-existent keys will return the default value. | ||
pub fn get(&self, key: &K) -> V { | ||
self.values.get(key).unwrap_or_default() | ||
} | ||
|
||
fn pull_spread(ptr: &mut KeyPtr) -> Self { | ||
Self { | ||
value: SpreadLayout::pull_spread(ptr), | ||
/// Inserting into the map increases its length by one. | ||
pub fn set<I, U>(&mut self, key: I, value: &U) | ||
where | ||
I: scale::EncodeLike<K>, | ||
E: scale::EncodeLike<V> + Storable, | ||
{ | ||
if self.values.insert(key, value).is_none() { | ||
self.length += 1 | ||
} | ||
} | ||
|
||
fn push_spread(&self, ptr: &mut KeyPtr) { | ||
SpreadLayout::push_spread(&self.value, ptr); | ||
/// Removing a value from the map decreases its length by one. | ||
pub fn remove(&mut self, key: &K) { | ||
if self.values.take(key).is_some() { | ||
self.length -= 1 | ||
} | ||
} | ||
|
||
fn clear_spread(&self, ptr: &mut KeyPtr) { | ||
SpreadLayout::clear_spread(&self.value, ptr); | ||
/// Return how many values the mapping contains | ||
pub fn len(&self) -> u32 { | ||
self.length | ||
} | ||
} | ||
|
||
/// `DefaultMap` is compatible with contract storage. | ||
#[ink(storage)] | ||
pub struct MyContract { | ||
my_map: DefaultMap<BlockNumber, Balance>, | ||
} | ||
``` | ||
|
||
You can check what each method does in the [trait's docs](https://docs.rs/ink_storage/4.0.0-beta/ink_storage/traits/trait.SpreadLayout.html). Check how some data structures are implemented, such as [Mapping](https://docs.rs/ink_storage/4.0.0-beta/src/ink_storage/lazy/mapping.rs.html#113). | ||
:::caution | ||
|
||
Generic data types may substantially increase your contracts overall code size, making it | ||
more costly to store on-chain. | ||
|
||
The reason for this is [Rust's monomorphization](https://rustwasm.github.io/twiggy/concepts/generic-functions-and-monomorphization.html). | ||
|
||
::: | ||
|
139 changes: 96 additions & 43 deletions
139
versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,78 +1,131 @@ | ||
--- | ||
title: Working with Mapping | ||
title: Working with Mapping | ||
slug: /datastructures/mapping | ||
--- | ||
|
||
:::caution | ||
TODO | ||
In this section we demonstrate how to work with ink! [`Mapping`](https://docs.rs/ink_storage/4.0.0-beta.1/ink_storage/struct.Mapping.html). | ||
|
||
Beware, this page is no longer up to date for 4.0! | ||
::: | ||
|
||
In this section we want to demonstrate how to work with ink! [`Mapping`](https://docs.rs/ink_storage/4.0.0-beta/ink_storage/struct.Mapping.html). | ||
|
||
Here is an example of a mapping from a user to a number: | ||
Here is an example of a mapping from a user to a `Balance`: | ||
|
||
```rust | ||
#[ink(storage)] | ||
#[derive(SpreadAllocate)] | ||
pub struct MyContract { | ||
// Store a mapping from AccountIds to a u32 | ||
map: ink_storage::Mapping<AccountId, u32>, | ||
/// Assign a balance to every account. | ||
balances: ink::storage::Mapping<AccountId, Balance>, | ||
} | ||
``` | ||
|
||
This means that for a given key, you can store a unique instance of a value type. In this | ||
case, each "user" gets their own number. | ||
|
||
## Initializing a Mapping | ||
case, each "user" gets credited their own balance. | ||
|
||
In order to correctly initialize a `Mapping` we need two things: | ||
1. An implementation of the [`SpreadAllocate`](https://docs.rs/ink_storage/4.0.0-beta/ink_storage/traits/trait.SpreadAllocate.html) trait on our storage struct | ||
2. The [`ink_lang::utils::initalize_contract`](https://docs.rs/ink_lang/4.0.0-beta/ink_lang/utils/fn.initialize_contract.html) initializer | ||
## Example: Using a `Mapping` | ||
|
||
Not initializing storage before you use it is a common mistake that can break your smart | ||
contract. If you do not initialize your `Mapping`'s correctly you may end up with | ||
different `Mapping`'s operating on the same set of storage entries 😱. | ||
The following example contract utilizes a `Mapping` so that anyone can deposit and withdraw | ||
balance for their own account: | ||
|
||
```rust | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
#[ink::contract] | ||
mod mycontract { | ||
use ink_storage::traits::SpreadAllocate; | ||
use ink::storage::Mapping; | ||
|
||
#[ink(storage)] | ||
#[derive(SpreadAllocate)] | ||
pub struct MyContract { | ||
// Store a mapping from AccountIds to a u32 | ||
map: ink_storage::Mapping<AccountId, u32>, | ||
/// Assign a balance to every account ID | ||
balances: Mapping<AccountId, Balance>, | ||
} | ||
|
||
impl MyContract { | ||
#[ink(constructor)] | ||
pub fn new(count: u32) -> Self { | ||
// This call is required in order to correctly initialize the | ||
// `Mapping`s of our contract. | ||
ink_lang::utils::initialize_contract(|contract: &mut Self| { | ||
let caller = Self::env().caller(); | ||
contract.map.insert(&caller, &count); | ||
}) | ||
/// Constructor to initialize the contract with an empty mapping. | ||
#[ink(constructor, payable)] | ||
pub fn new() -> Self { | ||
let balances = Mapping::default(); | ||
Self { balances } | ||
} | ||
|
||
#[ink(constructor)] | ||
pub fn default() -> Self { | ||
// Even though we're not explicitly initializing the `Mapping`, | ||
// we still need to call this | ||
ink_lang::utils::initialize_contract(|_| {}) | ||
/// Retrieve the balance of the caller. | ||
#[ink(message)] | ||
pub fn get_balance(&self) -> Option<Balance> { | ||
let caller = self.env().caller(); | ||
self.balances.get(caller) | ||
} | ||
|
||
// Grab the number at the caller's AccountID, if it exists | ||
#[ink(message)] | ||
pub fn get(&self) -> u32 { | ||
let caller = Self::env().caller(); | ||
self.map.get(&caller).unwrap_or_default() | ||
/// Credit more money to the contract. | ||
#[ink(message, payable)] | ||
pub fn transfer(&mut self) { | ||
let caller = self.env().caller(); | ||
let balance = self.balances.get(caller).unwrap_or(0); | ||
let endowment = self.env().transferred_value(); | ||
self.balances.insert(caller, &(balance + endowment)); | ||
} | ||
|
||
/// Withdraw all your balance from the contract. | ||
pub fn withdraw(&mut self) { | ||
let caller = self.env().caller(); | ||
let balance = self.balances.get(caller).unwrap(); | ||
self.balances.remove(caller); | ||
self.env().transfer(caller, balance).unwrap() | ||
} | ||
} | ||
} | ||
|
||
``` | ||
|
||
## Considerations when using the `Mapping` type | ||
|
||
One of the main purposes of the ink! `Mapping` is to allow storing a lot of values. | ||
|
||
:::note | ||
|
||
There are many additional datastructures accessible under `ink::prelude::collections`, such | ||
such as `HashMap` or `BTreeMap` (to name a few). Note that these datastructures all exhibit | ||
`Packed` storage loading behavior, as opposed to the ink! `Mapping`! | ||
|
||
::: | ||
|
||
### Storage loading behaviour | ||
|
||
Each `Mapping` value lives under it's own storage key. Briefly, this means that `Mapping`s | ||
are lazily loaded in ink!. In other words, if your message only accesses a single key of a | ||
mapping, it will not load the whole mapping but only the value being accessed. | ||
|
||
HCastano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
```rust | ||
// This causes only a single storage access and the decoding of a single "MyValue" struct, | ||
// no matter how many elements there are inside the mapping. | ||
let foo: MyValue = my_mapping.get(0)?; | ||
|
||
for n in 0..5 { | ||
// This causes a storage access and a decoding operation for each loop iteration. | ||
// It is not possible to "fetch" all key/value pairs directly at once. | ||
let bar: MyValue = my_mapping.get(n)?; | ||
} | ||
``` | ||
|
||
Furthermore, it follows that mapping values do not have a contiguous storage layout and it is | ||
not possible to iterate over the contents of a map. | ||
|
||
|
||
### Updating values | ||
|
||
The attentive reader may have noticed that accessing mapping values via the `Mapping::get()` | ||
method will result in an owned value (a local copy), as opposed to a direct reference | ||
into the storage. Changes to this value won't be reflected in the contracts storage | ||
"automatically". To avoid this common pitfall, the value must be inserted again at the same | ||
key after it was modified. The `transfer` function from above example illustrates this: | ||
|
||
```rust | ||
pub fn transfer(&mut self) { | ||
let caller = self.env().caller(); | ||
// `balance` is a local value and not a reference to the value on storage! | ||
let balance = self.balances.get(caller).unwrap_or(0); | ||
let endowment = self.env().transferred_value(); | ||
// The following line of code would have no effect to the balance of the | ||
// caller stored in contract storage: | ||
// | ||
// balance += endowment; | ||
// | ||
// Instead, we use the `insert` function to write it back like so: | ||
self.balances.insert(caller, &(balance + endowment)); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Perhaps we could merge the "Pre-ink! v4.0.0-beta storage" chapter here, to provide information about how new traits differ from the old traits?