Skip to content
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 78 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
33cbdf7
WIP
xermicus Jan 23, 2023
bac0ba0
eager loading best explained in storage layout section
xermicus Jan 23, 2023
fc0a0b9
rename to storage-layout
xermicus Jan 23, 2023
e83dcc3
WIP update mappings
xermicus Jan 23, 2023
daf100a
update mappings
xermicus Jan 23, 2023
a985a0b
WIP storage layout
xermicus Jan 23, 2023
bbf7610
explain packed layout
xermicus Jan 24, 2023
ee9fa22
WIP
xermicus Jan 24, 2023
1ef577b
explain lazy
xermicus Jan 24, 2023
270f826
update example
xermicus Jan 24, 2023
07398fb
WIP custom storage types
xermicus Jan 24, 2023
16d86db
custom storage types
xermicus Jan 24, 2023
037c35b
WIP metadata storage docs
xermicus Jan 24, 2023
009a0d1
update storage layout in metadata
xermicus Jan 25, 2023
fc05550
typos
xermicus Jan 25, 2023
c873ff0
Update versioned_docs/version-4.0.0-alpha.1/datastructures/custom.md
xermicus Jan 26, 2023
d39bf54
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 26, 2023
a2d4880
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 26, 2023
c5ee9db
Update versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
xermicus Jan 27, 2023
14d4b8a
Update versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
xermicus Jan 27, 2023
d23196d
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus Jan 27, 2023
26ffbb3
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus Jan 27, 2023
b75865e
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus Jan 27, 2023
601cb69
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
278b18d
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
37dd49a
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
855c005
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
3889490
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus Jan 27, 2023
55ca0a9
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus Jan 27, 2023
0d06b29
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus Jan 27, 2023
3be2041
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
de68341
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
ed67c09
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
6a86095
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
3282161
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
a551e16
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 27, 2023
8dbce95
rework mapping overview
xermicus Jan 29, 2023
055a411
rework lazy overview
xermicus Jan 29, 2023
5685d84
add mapping example for local variable
xermicus Jan 29, 2023
76bc8ae
mapping storage access example
xermicus Jan 29, 2023
852607a
explain why iteration over mapping might be expensive
xermicus Jan 29, 2023
d1bf3a8
small fixes
xermicus Jan 29, 2023
9832d47
more fixes
xermicus Jan 29, 2023
54954a5
wip transparent hashing
xermicus Jan 30, 2023
e19ead9
fix layout of storage in metadata section
xermicus Jan 30, 2023
69544a3
Update versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
xermicus Jan 30, 2023
a4edead
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus Jan 30, 2023
125b4e0
Update versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
xermicus Jan 30, 2023
de865b3
Update versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
xermicus Jan 30, 2023
475082c
smol fix
xermicus Jan 30, 2023
0c59044
explain mapping loading behavior first
xermicus Jan 30, 2023
9448a2e
mention other datastructures under collections prelude
xermicus Jan 30, 2023
7c0826f
mention that the ink mapping can store a lot of values
xermicus Jan 30, 2023
ad310b3
mention pitfall of the contract trapping when decoding values too large
xermicus Jan 30, 2023
213762d
refer to ink::prelude::vec::Vec
xermicus Jan 30, 2023
c61c021
Update versioned_docs/version-4.0.0-alpha.1/datastructures/custom.md
xermicus Jan 31, 2023
9b07b1f
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 31, 2023
f8fca53
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 31, 2023
8f6fbe4
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus Jan 31, 2023
9464011
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 31, 2023
78a0e29
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus Jan 31, 2023
4cdf361
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 31, 2023
a5998c4
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus Jan 31, 2023
12f2884
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-la…
xermicus Jan 31, 2023
f6fb885
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus Jan 31, 2023
3447c94
Update versioned_docs/version-4.0.0-alpha.1/datastructures/storage-in…
xermicus Jan 31, 2023
f1d2eac
Merge branch 'master' into cl/storage-docs
xermicus Feb 1, 2023
53acaea
Apply suggestions from code review
xermicus Feb 1, 2023
e02fab3
impl nandos comments
xermicus Feb 1, 2023
ac6d467
impl greens comments
xermicus Feb 1, 2023
3418267
fix oopise
xermicus Feb 1, 2023
8e0891d
link ink::storage_item macro docs
xermicus Feb 1, 2023
6942184
Fix some typos
HCastano Feb 2, 2023
04fd497
Couple of small nits
HCastano Feb 2, 2023
e22dab2
Fix some typos
HCastano Feb 2, 2023
ce6bb9f
Remove trailing whitespaces for entire PR
HCastano Feb 2, 2023
0eb238a
Merge branch 'master' into cl/storage-docs
xermicus Feb 2, 2023
5481b4d
better explain storage_item macro
xermicus Feb 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
578 changes: 437 additions & 141 deletions static/img/kv.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 100 additions & 23 deletions versioned_docs/version-4.0.0-alpha.1/datastructures/custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link

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?

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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 versioned_docs/version-4.0.0-alpha.1/datastructures/mapping.md
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));
}
```
65 changes: 22 additions & 43 deletions versioned_docs/version-4.0.0-alpha.1/datastructures/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,25 @@ title: Overview
slug: /datastructures/overview
---

The `ink_storage` crate acts as the standard storage library for ink! smart contracts. At
the moment it only provides a single low-level primitive for interacting with storage,
the [`Mapping`](https://docs.rs/ink_storage/4.0.0-beta/ink_storage/struct.Mapping.html).

The `Mapping` is a mapping of key-value pairs directly to the contract storage. Its main advantage
is to be simple and lightweight. As such, it does not provide any high-level
functionality, such as iteration or automatic clean-up. Smart contract authors will need
to implement any high level functionality themselves.

## Eager Loading

When executing a contract, all the fields of the `#[ink(storage)]` struct will be pulled
from storage, regardless of whether or not they are used during the message execution.

Smart contract authors should be aware of this behaviour since it could potentially
affect their contract performance. For example, consider the following storage struct:

```rust
#[ink(storage)]
pub struct EagerLoading {
a: i32,
b: ink_prelude::vec::Vec<i32>,
}

impl EagerLoading {
#[ink(message)]
pub fn read_a(&self) {
let a = self.a;
}
}
```

In `EagerLoading::read_a()` we only read the `a` storage item. However, the `b` storage
item will still be loaded from storage. As a reminder, this means accessing the
underlying database and SCALE decoding the value. This can incur high costs, especially
as the number of elements in `b` grows.

:::note

Eager loading does **not** apply to `Mapping` fields, though, as key lookups in mappings
are done directly from contract storage.

:::
The `ink_storage` crate acts as the standard storage library for ink! smart contracts.
At the moment it provides two primitives for interacting with storage,
[`Mapping`](https://docs.rs/ink_storage/4.0.0-beta.1/ink_storage/struct.Mapping.html)
and [`Lazy`](https://docs.rs/ink_storage/4.0.0-beta.1/ink_storage/struct.Lazy.html).

`Mapping` is a mapping of key-value pairs directly to the contract storage. It is very
similar to traditional hash tables and comparable to the `mapping` type Solidity offers.
As a core ingredient to the ink! language, its main advantage is being simple and
lightweight: It favors being efficient in terms of gas costs and code size
over providing a lot of high-level functionality found in other implementations
like the `ink::prelude::collections::HashMap` type.
Overall, the ink! `Mapping` will be solid choice for most contracts. Moreover, smart
contracts developers can implement advanced features themselves.

`Lazy` is a wrapper type that can be used over any other storage compatible type.
This allows smart contract developers fine grained manual control over the layout of
the contract storage by assigning a separate storage cell for the field. For example,
it can be used to prevent the contract from eagerly loading large storage fields
during each contract call.
Conceivably, it may be desirable to change certain aspects on how your contract deals with
its storage variables. You can find out more about this in the section about the ink!
[Storage Layout](https://use.ink/versioned_docs/version-4.0.0-alpha.1/datastructures/storage-layout).
Loading