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 to reflect collections and lazy removal #44

Merged
merged 11 commits into from
Feb 16, 2022
3 changes: 1 addition & 2 deletions docs/basics/cross-contract-calling.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ into our calling smart contract.
The calling contract looks like this:

```rust
use ink_storage::Lazy;
use other_contract::OtherContract;

//--snip--
#[ink(storage)]
struct MyContract {
/// The other contract.
other_contract: Lazy<OtherContract>,
other_contract: OtherContract,
}

impl MyContract {
Expand Down
42 changes: 3 additions & 39 deletions docs/basics/mutating-values.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ You may have noticed that the function template included `self` as the first par
contract functions. It is through `self` that you gain access to all your contract functions and
storage items.

If you are simply _reading_ from the contract storage, you only need to pass `&self`. But if you want to _modify_ storage items, you will need to explicitly mark it as mutable, `&mut self`.
If you are simply _reading_ from the contract storage, you only need to pass `&self`. But
if you want to _modify_ storage items, you will need to explicitly mark it as mutable,
`&mut self`.

```rust
impl MyContract {
Expand All @@ -26,41 +28,3 @@ impl MyContract {
}
}
```

## Lazy Storage Values

There is [a `Lazy` type](https://paritytech.github.io/ink/ink_storage/struct.Lazy.html) that can be
used for ink! storage values that do not need to be loaded in some or most cases. Many simple ink!
examples do not require the use of `Lazy` values. Since there is
some overhead associated with `Lazy` values, they should only be used where required.

This is an example of using the `Lazy` type:

```rust
#[ink(storage)]
pub struct MyContract {
// Store some number
my_number: ink_storage::Lazy<u32>,
}

impl MyContract {
#[ink(constructor)]
pub fn new(init_value: i32) -> Self {
Self {
my_number: ink_storage::Lazy::<u32>::new(init_value),
}
}

#[ink(message)]
pub fn my_setter(&mut self, new_value: u32) {
ink_storage::Lazy::<u32>::set(&mut self.my_number, new_value);
}

#[ink(message)]
pub fn my_adder(&mut self, add_value: u32) {
let my_number = &mut self.my_number;
let cur = ink_storage::Lazy::<u32>::get(my_number);
ink_storage::Lazy::<u32>::set(my_number, cur + add_value);
}
}
```
13 changes: 7 additions & 6 deletions docs/basics/storing-values.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ Substrate contracts may store types that are encodable and decodable with
types such as `bool`, `u{8,16,32,64,128}`, `i{8,16,32,64,128}`, `String`, tuples, and arrays.

ink! provides Substrate specific types like `AccountId`, `Balance`, and `Hash` to smart contracts as if
they were primitive types. ink! also provides storage types for more elaborate storage interactions through the storage module:
they were primitive types.

```rust
use ink_storage::collections::{Vec, HashMap, Stash, Bitvec};
```

ink! also provides a `Mapping` storage type. You can read more about it [here](https://paritytech.github.io/ink-docs/datastructures/mapping).

Here is an example of how you would store an `AccountId` and `Balance`:

Expand All @@ -50,13 +49,15 @@ mod MyContract {
}
```

You can find all the supported Substrate types in the [`ink_storage` crate](/~https://github.com/paritytech/ink/tree/master/crates/storage).

## Initializing Storage in Constructors

Constructors are how values get initialized.
Every ink! smart contract must have a constructor which is run once when a contract is created. ink! smart contracts can have multiple constructors:

Note that if you have a contract whose storage contains `Mapping'`s you will need to use
`ink_lang::utils::initialize_contract` in your constructor. See the
[`Mapping` documentation](https://paritytech.github.io/ink-docs/datastructures/mapping) for more details.

```rust
use ink_lang as ink;

Expand Down
5 changes: 2 additions & 3 deletions docs/datastructures/custom.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Custom Datastructures
title: Custom Data Structures
slug: /datastructures/custom-datastructure
---

Expand Down Expand Up @@ -40,6 +40,5 @@ impl SpreadLayout for Inner {
}

```
You can check what each method does at the [crate's docs](https://paritytech.github.io/ink/src/ink_storage/collections/hashmap/storage.rs.html#113-139). Checking how some data structures are implemented, such as [HashMap](https://paritytech.github.io/ink/src/ink_storage/collections/hashmap/storage.rs.html#113-139), might be useful too.

In the future we plan on providing some more ink! workshops and tutorials guiding the approach to design and implement a custom storage data structure.
You can check what each method does in the [trait's docs](https://paritytech.github.io/ink/ink_storage/traits/trait.SpreadLayout.html). Check how some data structures are implemented, such as [Mapping](https://paritytech.github.io/ink/src/ink_storage/lazy/mapping.rs.html#131-156).
75 changes: 75 additions & 0 deletions docs/datastructures/mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
title: Working with Mapping
slug: /datastructures/mapping
---

In this section we want to demonstrate how to work with ink! [`Mapping`](https://paritytech.github.io/ink/ink_storage/struct.Mapping.html).

Here is an example of a mapping from a user to a number:

```rust
#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct MyContract {
// Store a mapping from AccountIds to a u32
map: ink_storage::Mapping<AccountId, u32>,
}
```

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

In order to correctly initialize a `Mapping` we need two things:
1. An implementation of the [`SpreadAllocate`](https://paritytech.github.io/ink/ink_storage/traits/trait.SpreadAllocate.html) trait on our storage struct
2. The [`ink_lang::utils::initalize_contract`](https://paritytech.github.io/ink/ink_lang/codegen/fn.initialize_contract.html) initializer

Not initializing storage before you use it is a common error 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 😱.

```rust

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
mod mycontract {
use ink_storage::traits::SpreadAllocate;

#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct MyContract {
// Store a mapping from AccountIds to a u32
map: ink_storage::Mapping<AccountId, u32>,
}

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);
})
}

#[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(|_| {})
}

// 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()
}
}
}
```
15 changes: 10 additions & 5 deletions docs/datastructures/opting-out.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ title: Opt out of Storage
slug: /datastructures/opting-out
---

If you are in need of storing some temporary information across method and message boundaries ink! will have your back with the `ink_storage::Memory` abstraction. It allows you to simply opt-out of using the storage for the wrapped entity at all and thus is very similar to Solidity's very own `memory` annotation.
If you are in need of storing some temporary information across method and message
boundaries ink! will have your back with the `ink_storage::Memory` abstraction. It allows
you to simply opt-out of using the storage for the wrapped entity at all and thus is very
similar to Solidity's very own `memory` annotation.

An example below:

```rust
#[ink(storage)]
pub struct OptedOut {
a: i32,
b: ink_storage::Lazy<i32>,
c: ink_storage::Memory<i32>,
b: ink_storage::Memory<i32>,
}
```

The the above example `a` and `b` are normal storage entities, however, `c` on the other hand side will never load from or store to contract storage and will always be reset to the default value of its `i32` type for every contract call.
It can be accessed from all ink! messages or methods via `self.c`, but will never manipulate the contract storage and thus acts wonderfully as some shared local information.
The the above example `a` is a normal storage entry, however, `b` on the other
hand side will never load from or store to contract storage and will always be reset to
the default value of its `i32` type for every contract call. It can be accessed from all
ink! messages or methods via `self.b`, but will never manipulate the contract storage and
thus acts wonderfully as some shared local information.
81 changes: 21 additions & 60 deletions docs/datastructures/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,39 @@ title: Overview
slug: /datastructures/overview
---

The `ink_storage` crate acts as the standard storage library for ink! smart contracts.
It provides all the necessary tools and data structures to organize and operate the contract's storage intuitively and efficiently.
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://paritytech.github.io/ink/ink_storage/struct.Mapping.html).

[You can find the crates documentation for all ink! data structures here.](https://paritytech.github.io/ink/ink_storage/collections/)
The `Mapping` is a mapping of key-value pairs directly to the contract storage. Its main
is to be simple and lighweight. 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.

At the moment we provide these data structures:
## Eager Loading

* [`BinaryHeap`](https://paritytech.github.io/ink/ink_storage/collections/struct.BinaryHeap.html): A priority queue implemented with a binary heap.
* [`BitStash`](https://paritytech.github.io/ink/ink_storage/collections/struct.BitStash.html) A stash for bits operating on the contract storage.
* [`Bitvec`](https://paritytech.github.io/ink/ink_storage/collections/struct.Bitvec.html) A storage bit vector.
* [`HashMap`](https://paritytech.github.io/ink/ink_storage/collections/struct.HashMap.html) A hash map operating on the contract storage.
* [`SmallVec`](https://paritytech.github.io/ink/ink_storage/collections/struct.SmallVec.html) A contiguous growable array type.
* [`Stash`](https://paritytech.github.io/ink/ink_storage/collections/struct.Stash.html) A stash data structure operating on contract storage.
* [`Vec`](https://paritytech.github.io/ink/ink_storage/collections/struct.Vec.html) A contiguous growable array type, written `Vec<T>` but pronounced 'vector'.
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.

Data structures provided by the `ink_storage` crate are inherently lazy;
they are either high-level lazy or low-level lazy data structures.
The difference between high-level and low-level lies in the distinction in how these data structures are aware
of the elements that they operate on.
Smart contract authors should be aware of this behaviour since it could potentially
affect their contract performance. For example, consider the following storage struct:

For <em>high-level</em> data structures they are fully aware about the elements they contain, do all the clean-up by themselves so the user can concentrate on the business logic.

For <em>low-level</em> data structures the responsibility about the elements lies in the hands of the contract author.
Also they operate on cells (`Option<T>`) instead of entities of type `T`. But what does that mean exactly?

The `ink_storage::Lazy` type caches their entities and acts lazily on the storage.
This means that a read or write operation is only performed when it really needs to
in order to satisfy other inputs.

Data types such as Rust primitives `i32` or Rust's very own `Vec` or data structures
can also be used to operate on the contract's storage, however, they will load their
contents eagerly which is often not what you want.

An example follows with the below contract storage and a message that operates on either of the two fields.
```rust
#[ink(storage)]
pub struct TwoValues {
offset: i32,
pub struct EagerLoading {
a: i32,
b: i32,
b: ink_prelude::vec::Vec<i32>,
}

impl TwoValues {
impl EagerLoading {
#[ink(message)]
pub fn set(&mut self, which: bool, new_value: i32) {
match which {
true => { self.a = self.offset + new_value; },
false => { self.b = self.offset + new_value; },
}
pub fn read_a(&self) {
let a = self.a;
}
}
```

Whenever we call `TwoValues::set` always both `a` and `b` are loaded despite the fact the we only operate on one of them at a time. This is very costly since storage accesses are in fact database look-ups.
In order to prevent this eager loading of storage contents we can make use of `ink_storage::Lazy` or other lazy data structures defined in that crate:
```rust
#[ink(storage)]
pub struct TwoValues {
offset: i32,
a: ink_storage::Lazy<i32>,
b: ink_storage::Lazy<i32>,
}

impl TwoValues {
#[ink(message)]
pub fn set(&mut self, which: bool, new_value: i32) {
match which {
true => Lazy::set(&self.a, self.offset + new_value),
false => Lazy::set(&self.b, self.offset + new_value),
}
}
}
```
Now `a` and `b` are only loaded when the contract really needs their values.
Note that `offset` remained `i32` since it is always needed and could spare the minor overhead of the `ink_storage::Lazy` wrapper.
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.
Loading