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

Simple Mapping Storage Primitive #946

Merged
merged 42 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
61d564d
Add `Mapping` storage collection
HCastano Sep 29, 2021
8ae8047
Implement `insert` and `get` for `Mapping`
HCastano Sep 29, 2021
3a9d97e
Implement `SpreadLayout` for `Mapping`
HCastano Sep 29, 2021
3e29a25
Fix typo
HCastano Sep 29, 2021
d1387f7
Add some basic tests
HCastano Sep 29, 2021
9e48c18
Fix some documentation formatting
HCastano Sep 29, 2021
b7cf4dc
Use `PackedLayout` as trait bound instead of `Encode/Decode`
HCastano Sep 30, 2021
dc87fba
Avoid using low level `ink_env` functions when interacting with storage
HCastano Sep 30, 2021
728462a
RustFmt
HCastano Sep 30, 2021
5b2a8af
Appease Clippy
HCastano Sep 30, 2021
a5d016e
Only use single `PhantomData` field
HCastano Sep 30, 2021
ad524c6
Change `get` API to take reference to `key`
HCastano Sep 30, 2021
e6293a3
Implement `TypeInfo` and `StorageLayout` for `Mapping`
HCastano Sep 30, 2021
b55a24e
Properly gate `TypeInfo` and `StorageLayout` impls behind `std`
HCastano Sep 30, 2021
9c6f853
Replace `HashMap` with `Mapping` in ERC-20 example
HCastano Sep 30, 2021
299ba86
Return `Option` from `Mapping::get`
HCastano Oct 1, 2021
78e69f3
Update ERC-20 to handle `Option` returns
HCastano Oct 1, 2021
00ce6ea
Merge branch 'master' into hc-simple-mapping-primitive
HCastano Oct 1, 2021
a43e0de
Merge branch 'master' into hc-simple-mapping-primitive
HCastano Oct 11, 2021
7c55dbf
Change `get` and `key` to use `Borrow`-ed values
HCastano Oct 11, 2021
84576ed
Add `Debug` and `Default` implementations
HCastano Oct 11, 2021
a5d01e4
Proper spelling
HCastano Oct 11, 2021
14c4347
Change `insert` to only accept borrowed K,V pairs
HCastano Oct 13, 2021
5508fc1
Update ERC-20 example accordingly
HCastano Oct 13, 2021
baa2c32
Make more explicit what each `key` is referring to
HCastano Oct 13, 2021
cede033
Try using a `RefCell` instead of passing `Key` around
HCastano Oct 13, 2021
e5c6ef5
Try using `UnsafeCell` instead
HCastano Oct 13, 2021
0135977
Revert "Try using a `RefCell` instead of passing `Key` around"
HCastano Oct 14, 2021
ad26982
Merge branch 'master' into hc-simple-mapping-primitive
HCastano Oct 19, 2021
644ab71
Clean up some of the documentation
HCastano Oct 19, 2021
2ed985f
Merge branch 'master' into hc-simple-mapping-primitive
HCastano Nov 4, 2021
b954162
Simple Mapping type improvements (#979)
Robbepop Nov 9, 2021
7e55e56
Merge branch 'master' into hc-simple-mapping-primitive
HCastano Nov 9, 2021
507cf5b
Merge branch 'master' into hc-simple-mapping-primitive
HCastano Nov 9, 2021
c23e679
Use new `initialize_contract()` function
HCastano Nov 9, 2021
334a9da
Derive `SpreadAllocate` for `ink(storage)` structs
HCastano Nov 9, 2021
408bf81
Stop manually implementing SpreadAllocate for ERC-20
HCastano Nov 9, 2021
b625a0a
Stop implementing `SpreadAllocate` in the storage codegen
HCastano Nov 9, 2021
2184cd6
Derive `SpreadAllocate` manually for ERC-20
HCastano Nov 9, 2021
1854dcf
RustFmt example
HCastano Nov 9, 2021
c4a951c
Move `Mapping` from `collections` to `lazy`
HCastano Nov 12, 2021
5e5a918
Remove extra `0` in docs
HCastano Nov 12, 2021
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
152 changes: 152 additions & 0 deletions crates/storage/src/collections/mapping/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2018-2021 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A simple mapping to contract storage.

use crate::traits::{
clear_spread_root,
pull_packed_root_opt,
pull_spread_root,
push_packed_root,
push_spread_root,
ExtKeyPtr,
KeyPtr,
PackedLayout,
SpreadLayout,
};

use core::marker::PhantomData;

use ink_env::hash::{
Blake2x256,
HashOutput,
};
use ink_primitives::Key;

/// A mapping of key-value pairs directly into contract storage.
///
/// If a key does not exist the `Default` value for the `value` will be returned.
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub struct Mapping<K, V> {
key: Key,
_marker: PhantomData<(K, V)>,
}

impl<K, V> Mapping<K, V>
where
K: PackedLayout,
V: PackedLayout,
{
/// Creates a new empty `Mapping`.
///
/// Not sure how this should be exposed/initialize irl.
pub fn new(key: Key) -> Self {
Self {
key,
_marker: Default::default(),
}
}

/// Insert the given `value` to the contract storage.
pub fn insert(&mut self, key: K, value: V) {
HCastano marked this conversation as resolved.
Show resolved Hide resolved
push_packed_root(&value, &self.key(&key));
}
HCastano marked this conversation as resolved.
Show resolved Hide resolved

/// Get the `value` at `key` from the contract storage.
///
/// Returns `None` if no `value` exists at the given `key`.
pub fn get(&self, key: &K) -> Option<V> {
pull_packed_root_opt(&self.key(key))
}
HCastano marked this conversation as resolved.
Show resolved Hide resolved

fn key(&self, key: &K) -> Key {
HCastano marked this conversation as resolved.
Show resolved Hide resolved
let encodedable_key = (self.key, key);
HCastano marked this conversation as resolved.
Show resolved Hide resolved
let mut output = <Blake2x256 as HashOutput>::Type::default();
ink_env::hash_encoded::<Blake2x256, _>(&encodedable_key, &mut output);
output.into()
}
HCastano marked this conversation as resolved.
Show resolved Hide resolved
}

impl<K, V> SpreadLayout for Mapping<K, V> {
const FOOTPRINT: u64 = 1;
const REQUIRES_DEEP_CLEAN_UP: bool = false;

#[inline]
fn pull_spread(ptr: &mut KeyPtr) -> Self {
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
pull_spread_root::<Self>(root_key)
}

#[inline]
fn push_spread(&self, ptr: &mut KeyPtr) {
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
push_spread_root::<Self>(self, root_key);
}

#[inline]
fn clear_spread(&self, ptr: &mut KeyPtr) {
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
clear_spread_root::<Self>(self, root_key);
}
}

#[cfg(feature = "std")]
const _: () = {
use crate::traits::StorageLayout;
use ink_metadata::layout::{
CellLayout,
Layout,
LayoutKey,
};

impl<K, V> StorageLayout for Mapping<K, V>
where
K: scale_info::TypeInfo + 'static,
V: scale_info::TypeInfo + 'static,
{
fn layout(key_ptr: &mut KeyPtr) -> Layout {
Layout::Cell(CellLayout::new::<Self>(LayoutKey::from(
key_ptr.advance_by(1),
)))
}
}
};

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn insert_and_get_work() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut mapping = Mapping::new([0u8; 32].into());
mapping.insert(1, 2);
assert_eq!(mapping.get(&1), Some(2));

Ok(())
})
.unwrap()
}

#[test]
fn gets_default_if_no_key_set() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mapping: Mapping<u8, u8> = Mapping::new([0u8; 32].into());
assert_eq!(mapping.get(&1), None);

Ok(())
})
.unwrap()
}
}
2 changes: 2 additions & 0 deletions crates/storage/src/collections/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod binary_heap;
pub mod bitstash;
pub mod bitvec;
pub mod hashmap;
pub mod mapping;
pub mod smallvec;
pub mod stash;
pub mod vec;
Expand All @@ -32,6 +33,7 @@ pub use self::{
bitstash::BitStash,
bitvec::Bitvec,
hashmap::HashMap,
mapping::Mapping,
stash::Stash,
vec::Vec,
};
Expand Down
4 changes: 2 additions & 2 deletions crates/storage/src/traits/spread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub trait SpreadLayout {
///
/// # Examples
///
/// An instance of type `i32` requires one storage cell, so its footprint is
/// 1. An instance of type `(i32, i32)` requires 2 storage cells since a
/// An instance of type `i32` requires one storage cell, so its footprint is 1.
/// An instance of type `(i32, i32)` requires 2 storage cells since a
/// tuple or any other combined data structure always associates disjunctive
/// cells for its sub types. The same applies to arrays, e.g. `[i32; 5]`
/// has a footprint of 5.
Expand Down
14 changes: 7 additions & 7 deletions examples/erc20/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use ink_lang as ink;
mod erc20 {
#[cfg(not(feature = "ink-as-dependency"))]
use ink_storage::{
collections::HashMap as StorageHashMap,
collections::mapping::Mapping,
lazy::Lazy,
};

Expand All @@ -16,10 +16,10 @@ mod erc20 {
/// Total token supply.
total_supply: Lazy<Balance>,
/// Mapping from owner to number of owned token.
balances: StorageHashMap<AccountId, Balance>,
balances: Mapping<AccountId, Balance>,
/// Mapping of the token amount which an account is allowed to withdraw
/// from another account.
allowances: StorageHashMap<(AccountId, AccountId), Balance>,
allowances: Mapping<(AccountId, AccountId), Balance>,
}

/// Event emitted when a token transfer occurs.
Expand Down Expand Up @@ -61,12 +61,12 @@ mod erc20 {
#[ink(constructor)]
pub fn new(initial_supply: Balance) -> Self {
let caller = Self::env().caller();
let mut balances = StorageHashMap::new();
let mut balances = Mapping::new([1u8; 32].into());
balances.insert(caller, initial_supply);
let instance = Self {
total_supply: Lazy::new(initial_supply),
balances,
allowances: StorageHashMap::new(),
allowances: Mapping::new([1u8; 32].into()),
};
Self::env().emit_event(Transfer {
from: None,
Expand All @@ -87,15 +87,15 @@ mod erc20 {
/// Returns `0` if the account is non-existent.
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balances.get(&owner).copied().unwrap_or(0)
self.balances.get(&owner).unwrap_or_default() // .copied().unwrap_or(0)
}

/// Returns the amount which `spender` is still allowed to withdraw from `owner`.
///
/// Returns `0` if no allowance has been set `0`.
#[ink(message)]
pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {
self.allowances.get(&(owner, spender)).copied().unwrap_or(0)
self.allowances.get(&(owner, spender)).unwrap_or_default() //.copied().unwrap_or(0)
}

/// Transfers `value` amount of tokens from the caller's account to account `to`.
Expand Down