-
Notifications
You must be signed in to change notification settings - Fork 443
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
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
61d564d
Add `Mapping` storage collection
HCastano 8ae8047
Implement `insert` and `get` for `Mapping`
HCastano 3a9d97e
Implement `SpreadLayout` for `Mapping`
HCastano 3e29a25
Fix typo
HCastano d1387f7
Add some basic tests
HCastano 9e48c18
Fix some documentation formatting
HCastano b7cf4dc
Use `PackedLayout` as trait bound instead of `Encode/Decode`
HCastano dc87fba
Avoid using low level `ink_env` functions when interacting with storage
HCastano 728462a
RustFmt
HCastano 5b2a8af
Appease Clippy
HCastano a5d016e
Only use single `PhantomData` field
HCastano ad524c6
Change `get` API to take reference to `key`
HCastano e6293a3
Implement `TypeInfo` and `StorageLayout` for `Mapping`
HCastano b55a24e
Properly gate `TypeInfo` and `StorageLayout` impls behind `std`
HCastano 9c6f853
Replace `HashMap` with `Mapping` in ERC-20 example
HCastano 299ba86
Return `Option` from `Mapping::get`
HCastano 78e69f3
Update ERC-20 to handle `Option` returns
HCastano 00ce6ea
Merge branch 'master' into hc-simple-mapping-primitive
HCastano a43e0de
Merge branch 'master' into hc-simple-mapping-primitive
HCastano 7c55dbf
Change `get` and `key` to use `Borrow`-ed values
HCastano 84576ed
Add `Debug` and `Default` implementations
HCastano a5d01e4
Proper spelling
HCastano 14c4347
Change `insert` to only accept borrowed K,V pairs
HCastano 5508fc1
Update ERC-20 example accordingly
HCastano baa2c32
Make more explicit what each `key` is referring to
HCastano cede033
Try using a `RefCell` instead of passing `Key` around
HCastano e5c6ef5
Try using `UnsafeCell` instead
HCastano 0135977
Revert "Try using a `RefCell` instead of passing `Key` around"
HCastano ad26982
Merge branch 'master' into hc-simple-mapping-primitive
HCastano 644ab71
Clean up some of the documentation
HCastano 2ed985f
Merge branch 'master' into hc-simple-mapping-primitive
HCastano b954162
Simple Mapping type improvements (#979)
Robbepop 7e55e56
Merge branch 'master' into hc-simple-mapping-primitive
HCastano 507cf5b
Merge branch 'master' into hc-simple-mapping-primitive
HCastano c23e679
Use new `initialize_contract()` function
HCastano 334a9da
Derive `SpreadAllocate` for `ink(storage)` structs
HCastano 408bf81
Stop manually implementing SpreadAllocate for ERC-20
HCastano b625a0a
Stop implementing `SpreadAllocate` in the storage codegen
HCastano 2184cd6
Derive `SpreadAllocate` manually for ERC-20
HCastano 1854dcf
RustFmt example
HCastano c4a951c
Move `Mapping` from `collections` to `lazy`
HCastano 5e5a918
Remove extra `0` in docs
HCastano 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
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 |
---|---|---|
@@ -0,0 +1,189 @@ | ||
// 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. | ||
//! | ||
//! # Note | ||
//! | ||
//! This mapping doesn't actually "own" any data. | ||
//! Instead it is just a simple wrapper around the contract storage facilities. | ||
|
||
use crate::traits::{ | ||
pull_packed_root_opt, | ||
push_packed_root, | ||
ExtKeyPtr, | ||
KeyPtr, | ||
PackedLayout, | ||
SpreadAllocate, | ||
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. | ||
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] | ||
#[derive(Default)] | ||
pub struct Mapping<K, V> { | ||
offset_key: Key, | ||
_marker: PhantomData<fn() -> (K, V)>, | ||
} | ||
|
||
impl<K, V> core::fmt::Debug for Mapping<K, V> { | ||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { | ||
f.debug_struct("Mapping") | ||
.field("offset_key", &self.offset_key) | ||
.finish() | ||
} | ||
} | ||
|
||
impl<K, V> Mapping<K, V> { | ||
/// Creates a new empty `Mapping`. | ||
fn new(offset_key: Key) -> Self { | ||
Self { | ||
offset_key, | ||
_marker: Default::default(), | ||
} | ||
} | ||
} | ||
|
||
impl<K, V> Mapping<K, V> | ||
where | ||
K: PackedLayout, | ||
V: PackedLayout, | ||
{ | ||
/// Insert the given `value` to the contract storage. | ||
#[inline] | ||
pub fn insert<Q, R>(&mut self, key: Q, value: &R) | ||
where | ||
Q: scale::EncodeLike<K>, | ||
R: scale::EncodeLike<V> + PackedLayout, | ||
{ | ||
push_packed_root(value, &self.storage_key(key)); | ||
} | ||
|
||
/// Get the `value` at `key` from the contract storage. | ||
/// | ||
/// Returns `None` if no `value` exists at the given `key`. | ||
#[inline] | ||
pub fn get<Q>(&self, key: Q) -> Option<V> | ||
where | ||
Q: scale::EncodeLike<K>, | ||
{ | ||
pull_packed_root_opt(&self.storage_key(key)) | ||
} | ||
|
||
/// Returns a `Key` pointer used internally by the storage API. | ||
/// | ||
/// This key is a combination of the `Mapping`'s internal `offset_key` | ||
/// and the user provided `key`. | ||
fn storage_key<Q>(&self, key: Q) -> Key | ||
where | ||
Q: scale::EncodeLike<K>, | ||
{ | ||
let encodedable_key = (&self.offset_key, key); | ||
let mut output = <Blake2x256 as HashOutput>::Type::default(); | ||
ink_env::hash_encoded::<Blake2x256, _>(&encodedable_key, &mut output); | ||
output.into() | ||
} | ||
} | ||
|
||
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 { | ||
// Note: There is no need to pull anything from the storage for the | ||
// mapping type since it initializes itself entirely by the | ||
// given key pointer. | ||
Self::new(*ExtKeyPtr::next_for::<Self>(ptr)) | ||
} | ||
|
||
#[inline] | ||
fn push_spread(&self, ptr: &mut KeyPtr) { | ||
// Note: The mapping type does not store any state in its associated | ||
// storage region, therefore only the pointer has to be incremented. | ||
ptr.advance_by(Self::FOOTPRINT); | ||
} | ||
|
||
#[inline] | ||
fn clear_spread(&self, ptr: &mut KeyPtr) { | ||
// Note: The mapping type is not aware of its elements, therefore | ||
// it is not possible to clean up after itself. | ||
ptr.advance_by(Self::FOOTPRINT); | ||
} | ||
} | ||
|
||
impl<K, V> SpreadAllocate for Mapping<K, V> { | ||
#[inline] | ||
fn allocate_spread(ptr: &mut KeyPtr) -> Self { | ||
// Note: The mapping type initializes itself entirely by the key pointer. | ||
Self::new(*ExtKeyPtr::next_for::<Self>(ptr)) | ||
} | ||
} | ||
|
||
#[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<u8, _> = 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() | ||
} | ||
} |
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
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
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.
LGTM