Skip to content

Commit b954162

Browse files
RobbepopHCastano
andauthored
Simple Mapping type improvements (#979)
* Add `Mapping` storage collection * Implement `insert` and `get` for `Mapping` * Implement `SpreadLayout` for `Mapping` * Fix typo * Add some basic tests * Fix some documentation formatting * Use `PackedLayout` as trait bound instead of `Encode/Decode` * Avoid using low level `ink_env` functions when interacting with storage * RustFmt * Appease Clippy * Only use single `PhantomData` field * Change `get` API to take reference to `key` * Implement `TypeInfo` and `StorageLayout` for `Mapping` * Properly gate `TypeInfo` and `StorageLayout` impls behind `std` * Replace `HashMap` with `Mapping` in ERC-20 example * Return `Option` from `Mapping::get` * Update ERC-20 to handle `Option` returns * Change `get` and `key` to use `Borrow`-ed values * Add `Debug` and `Default` implementations * Proper spelling * Change `insert` to only accept borrowed K,V pairs * Update ERC-20 example accordingly * Make more explicit what each `key` is referring to * Try using a `RefCell` instead of passing `Key` around * Try using `UnsafeCell` instead * Revert "Try using a `RefCell` instead of passing `Key` around" This reverts commit cede033. Using `RefCell`/`UnsafeCell` doesn't reduce the contract size more than what we have now, and it introduced `unsafe` code. We believe the limiting factor here is the `Key` type definition anyways. * Clean up some of the documentation * adjust the Mapping type for the new SpreadAllocate trait * adjust ERC-20 example for changes in Mapping type * remove commented out code * add doc comment to new_init * make it possible to use references in more cases with Mapping * use references in more cases for ERC-20 example contract * remove unnecessary references in Mapping methods * refactor/improve pull_packed_root_opt utility method slightly * fix ERC-20 example contract The problem with *self.total_supply is that it may implicitly read from storage in case it has not yet read a value from storage whereas Lazy::set just writes the value to the Lazy instance. Co-authored-by: Hernando Castano <[email protected]> Co-authored-by: Hernando Castano <[email protected]>
1 parent 2ed985f commit b954162

File tree

3 files changed

+125
-69
lines changed

3 files changed

+125
-69
lines changed

crates/storage/src/collections/mapping/mod.rs

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,20 @@
1414

1515
//! A simple mapping to contract storage.
1616
//!
17-
//! This mapping doesn't actually "own" any data. Instead it is just a simple wrapper around the
18-
//! contract storage facilities.
17+
//! # Note
18+
//!
19+
//! This mapping doesn't actually "own" any data.
20+
//! Instead it is just a simple wrapper around the contract storage facilities.
1921
2022
use crate::traits::{
21-
clear_spread_root,
2223
pull_packed_root_opt,
23-
pull_spread_root,
2424
push_packed_root,
25-
push_spread_root,
2625
ExtKeyPtr,
2726
KeyPtr,
2827
PackedLayout,
28+
SpreadAllocate,
2929
SpreadLayout,
3030
};
31-
3231
use core::marker::PhantomData;
3332

3433
use ink_env::hash::{
@@ -42,7 +41,7 @@ use ink_primitives::Key;
4241
#[derive(Default)]
4342
pub struct Mapping<K, V> {
4443
offset_key: Key,
45-
_marker: PhantomData<(K, V)>,
44+
_marker: PhantomData<fn() -> (K, V)>,
4645
}
4746

4847
impl<K, V> core::fmt::Debug for Mapping<K, V> {
@@ -53,52 +52,49 @@ impl<K, V> core::fmt::Debug for Mapping<K, V> {
5352
}
5453
}
5554

56-
impl<K, V> Mapping<K, V>
57-
where
58-
K: PackedLayout,
59-
V: PackedLayout,
60-
{
55+
impl<K, V> Mapping<K, V> {
6156
/// Creates a new empty `Mapping`.
62-
///
63-
/// TODO [#961]: Ideally we improve how this is initialized by extending the
64-
/// `SpreadLayout`/`PackedLayout` traits for non-caching data structures.
65-
pub fn new(offset_key: Key) -> Self {
57+
fn new(offset_key: Key) -> Self {
6658
Self {
6759
offset_key,
6860
_marker: Default::default(),
6961
}
7062
}
63+
}
7164

65+
impl<K, V> Mapping<K, V>
66+
where
67+
K: PackedLayout,
68+
V: PackedLayout,
69+
{
7270
/// Insert the given `value` to the contract storage.
73-
pub fn insert<Q, R>(&mut self, key: &Q, value: &R)
71+
#[inline]
72+
pub fn insert<Q, R>(&mut self, key: Q, value: &R)
7473
where
75-
K: core::borrow::Borrow<Q>,
76-
Q: scale::Encode,
77-
V: core::borrow::Borrow<R>,
78-
R: PackedLayout,
74+
Q: scale::EncodeLike<K>,
75+
R: scale::EncodeLike<V> + PackedLayout,
7976
{
8077
push_packed_root(value, &self.storage_key(key));
8178
}
8279

8380
/// Get the `value` at `key` from the contract storage.
8481
///
8582
/// Returns `None` if no `value` exists at the given `key`.
86-
pub fn get<Q>(&self, key: &Q) -> Option<V>
83+
#[inline]
84+
pub fn get<Q>(&self, key: Q) -> Option<V>
8785
where
88-
K: core::borrow::Borrow<Q>,
89-
Q: scale::Encode,
86+
Q: scale::EncodeLike<K>,
9087
{
9188
pull_packed_root_opt(&self.storage_key(key))
9289
}
9390

9491
/// Returns a `Key` pointer used internally by the storage API.
9592
///
96-
/// This key is a combination of the `Mapping`'s internal `offset_key` and the user provided
97-
/// `key`.
98-
fn storage_key<Q>(&self, key: &Q) -> Key
93+
/// This key is a combination of the `Mapping`'s internal `offset_key`
94+
/// and the user provided `key`.
95+
fn storage_key<Q>(&self, key: Q) -> Key
9996
where
100-
K: core::borrow::Borrow<Q>,
101-
Q: scale::Encode,
97+
Q: scale::EncodeLike<K>,
10298
{
10399
let encodedable_key = (&self.offset_key, key);
104100
let mut output = <Blake2x256 as HashOutput>::Type::default();
@@ -113,20 +109,32 @@ impl<K, V> SpreadLayout for Mapping<K, V> {
113109

114110
#[inline]
115111
fn pull_spread(ptr: &mut KeyPtr) -> Self {
116-
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
117-
pull_spread_root::<Self>(root_key)
112+
// Note: There is no need to pull anything from the storage for the
113+
// mapping type since it initializes itself entirely by the
114+
// given key pointer.
115+
Self::new(*ExtKeyPtr::next_for::<Self>(ptr))
118116
}
119117

120118
#[inline]
121119
fn push_spread(&self, ptr: &mut KeyPtr) {
122-
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
123-
push_spread_root::<Self>(self, root_key);
120+
// Note: The mapping type does not store any state in its associated
121+
// storage region, therefore only the pointer has to be incremented.
122+
ptr.advance_by(Self::FOOTPRINT);
124123
}
125124

126125
#[inline]
127126
fn clear_spread(&self, ptr: &mut KeyPtr) {
128-
let root_key = ExtKeyPtr::next_for::<Self>(ptr);
129-
clear_spread_root::<Self>(self, root_key);
127+
// Note: The mapping type is not aware of its elements, therefore
128+
// it is not possible to clean up after itself.
129+
ptr.advance_by(Self::FOOTPRINT);
130+
}
131+
}
132+
133+
impl<K, V> SpreadAllocate for Mapping<K, V> {
134+
#[inline]
135+
fn allocate_spread(ptr: &mut KeyPtr) -> Self {
136+
// Note: The mapping type initializes itself entirely by the key pointer.
137+
Self::new(*ExtKeyPtr::next_for::<Self>(ptr))
130138
}
131139
}
132140

crates/storage/src/traits/optspec.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,19 @@ pub fn pull_packed_root_opt<T>(root_key: &Key) -> Option<T>
102102
where
103103
T: PackedLayout,
104104
{
105-
match ink_env::get_contract_storage::<T>(root_key)
106-
.expect("decoding does not match expected type")
107-
{
108-
Some(mut value) => {
109-
// In case the contract storage is occupied we handle
110-
// the Option<T> as if it was a T.
105+
ink_env::get_contract_storage::<T>(root_key)
106+
.unwrap_or_else(|error| {
107+
panic!(
108+
"failed to pull packed from root key {}: {:?}",
109+
root_key, error
110+
)
111+
})
112+
.map(|mut value| {
113+
// In case the contract storage is occupied at the root key
114+
// we handle the Option<T> as if it was a T.
111115
<T as PackedLayout>::pull_packed(&mut value, root_key);
112-
Some(value)
113-
}
114-
None => None,
115-
}
116+
value
117+
})
116118
}
117119

118120
pub fn push_packed_root_opt<T>(entity: Option<&T>, root_key: &Key)
@@ -128,7 +130,7 @@ where
128130
super::push_packed_root(value, root_key)
129131
}
130132
None => {
131-
// Clear the associated storage cell.
133+
// Clear the associated storage cell since the entity is `None`.
132134
ink_env::clear_contract_storage(root_key);
133135
}
134136
}

examples/erc20/lib.rs

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ use ink_lang as ink;
44

55
#[ink::contract]
66
mod erc20 {
7+
use ink_primitives::{
8+
Key,
9+
KeyPtr,
10+
};
711
use ink_storage::{
812
collections::mapping::Mapping,
913
lazy::Lazy,
14+
traits::SpreadAllocate,
1015
};
1116

1217
/// A simple ERC-20 contract.
@@ -55,24 +60,37 @@ mod erc20 {
5560
/// The ERC-20 result type.
5661
pub type Result<T> = core::result::Result<T, Error>;
5762

63+
impl SpreadAllocate for Erc20 {
64+
fn allocate_spread(ptr: &mut KeyPtr) -> Self {
65+
Self {
66+
total_supply: SpreadAllocate::allocate_spread(ptr),
67+
balances: SpreadAllocate::allocate_spread(ptr),
68+
allowances: SpreadAllocate::allocate_spread(ptr),
69+
}
70+
}
71+
}
72+
5873
impl Erc20 {
5974
/// Creates a new ERC-20 contract with the specified initial supply.
6075
#[ink(constructor)]
6176
pub fn new(initial_supply: Balance) -> Self {
77+
let root_key = Key::from([0x00; 32]);
78+
let mut key_ptr = KeyPtr::from(root_key);
79+
let mut instance = Self::allocate_spread(&mut key_ptr);
80+
instance.new_init(initial_supply);
81+
instance
82+
}
83+
84+
/// Default initializes the ERC-20 contract with the specified initial supply.
85+
fn new_init(&mut self, initial_supply: Balance) {
6286
let caller = Self::env().caller();
63-
let mut balances = Mapping::new([1u8; 32].into());
64-
balances.insert(&caller, &initial_supply);
65-
let instance = Self {
66-
total_supply: Lazy::new(initial_supply),
67-
balances,
68-
allowances: Mapping::new([1u8; 32].into()),
69-
};
87+
self.balances.insert(&caller, &initial_supply);
88+
Lazy::set(&mut self.total_supply, initial_supply);
7089
Self::env().emit_event(Transfer {
7190
from: None,
7291
to: Some(caller),
7392
value: initial_supply,
7493
});
75-
instance
7694
}
7795

7896
/// Returns the total token supply.
@@ -86,15 +104,41 @@ mod erc20 {
86104
/// Returns `0` if the account is non-existent.
87105
#[ink(message)]
88106
pub fn balance_of(&self, owner: AccountId) -> Balance {
89-
self.balances.get(&owner).unwrap_or_default() // .copied().unwrap_or(0)
107+
self.balance_of_impl(&owner)
108+
}
109+
110+
/// Returns the account balance for the specified `owner`.
111+
///
112+
/// Returns `0` if the account is non-existent.
113+
///
114+
/// # Note
115+
///
116+
/// Prefer to call this method over `balance_of` since this
117+
/// works using references which are more efficient in Wasm.
118+
#[inline]
119+
fn balance_of_impl(&self, owner: &AccountId) -> Balance {
120+
self.balances.get(owner).unwrap_or_default()
90121
}
91122

92123
/// Returns the amount which `spender` is still allowed to withdraw from `owner`.
93124
///
94125
/// Returns `0` if no allowance has been set `0`.
95126
#[ink(message)]
96127
pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {
97-
self.allowances.get(&(owner, spender)).unwrap_or_default() //.copied().unwrap_or(0)
128+
self.allowance_impl(&owner, &spender)
129+
}
130+
131+
/// Returns the amount which `spender` is still allowed to withdraw from `owner`.
132+
///
133+
/// Returns `0` if no allowance has been set `0`.
134+
///
135+
/// # Note
136+
///
137+
/// Prefer to call this method over `allowance` since this
138+
/// works using references which are more efficient in Wasm.
139+
#[inline]
140+
fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance {
141+
self.allowances.get((owner, spender)).unwrap_or_default()
98142
}
99143

100144
/// Transfers `value` amount of tokens from the caller's account to account `to`.
@@ -108,7 +152,7 @@ mod erc20 {
108152
#[ink(message)]
109153
pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> {
110154
let from = self.env().caller();
111-
self.transfer_from_to(from, to, value)
155+
self.transfer_from_to(&from, &to, value)
112156
}
113157

114158
/// Allows `spender` to withdraw from the caller's account multiple times, up to
@@ -120,7 +164,7 @@ mod erc20 {
120164
#[ink(message)]
121165
pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> {
122166
let owner = self.env().caller();
123-
self.allowances.insert(&(owner, spender), &value);
167+
self.allowances.insert((&owner, &spender), &value);
124168
self.env().emit_event(Approval {
125169
owner,
126170
spender,
@@ -151,12 +195,13 @@ mod erc20 {
151195
value: Balance,
152196
) -> Result<()> {
153197
let caller = self.env().caller();
154-
let allowance = self.allowance(from, caller);
198+
let allowance = self.allowance_impl(&from, &caller);
155199
if allowance < value {
156200
return Err(Error::InsufficientAllowance)
157201
}
158-
self.transfer_from_to(from, to, value)?;
159-
self.allowances.insert(&(from, caller), &(allowance - value));
202+
self.transfer_from_to(&from, &to, value)?;
203+
self.allowances
204+
.insert((&from, &caller), &(allowance - value));
160205
Ok(())
161206
}
162207

@@ -170,20 +215,21 @@ mod erc20 {
170215
/// the caller's account balance.
171216
fn transfer_from_to(
172217
&mut self,
173-
from: AccountId,
174-
to: AccountId,
218+
from: &AccountId,
219+
to: &AccountId,
175220
value: Balance,
176221
) -> Result<()> {
177-
let from_balance = self.balance_of(from);
222+
let from_balance = self.balance_of_impl(from);
178223
if from_balance < value {
179224
return Err(Error::InsufficientBalance)
180225
}
181-
self.balances.insert(&from, &(from_balance - value));
182-
let to_balance = self.balance_of(to);
183-
self.balances.insert(&to, &(to_balance + value));
226+
227+
self.balances.insert(from, &(from_balance - value));
228+
let to_balance = self.balance_of_impl(to);
229+
self.balances.insert(to, &(to_balance + value));
184230
self.env().emit_event(Transfer {
185-
from: Some(from),
186-
to: Some(to),
231+
from: Some(*from),
232+
to: Some(*to),
187233
value,
188234
});
189235
Ok(())

0 commit comments

Comments
 (0)