-
Notifications
You must be signed in to change notification settings - Fork 449
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 type improvements #979
Changes from all commits
70172ac
0ce829f
e7e7f5d
1fead2f
62a6f01
f25b560
9622ad5
73d4ab9
92a4ce6
e1650c2
60c3370
cfd4b03
60bd5cd
e173a4d
99cad62
047ae35
b880b5a
20395ab
955896b
de7e67f
623ba34
9d12813
387b752
5ce455c
e272f3d
b0ecb48
ebfaea5
e652165
91f65f1
8ee0882
8fcdbdf
89f5799
53e63b4
cf823d1
674a04d
46286d4
8da218f
9ee7ee6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -14,21 +14,20 @@ | |||||||
|
||||||||
//! A simple mapping to contract storage. | ||||||||
//! | ||||||||
//! This mapping doesn't actually "own" any data. Instead it is just a simple wrapper around the | ||||||||
//! contract storage facilities. | ||||||||
//! # Note | ||||||||
//! | ||||||||
//! This mapping doesn't actually "own" any data. | ||||||||
//! Instead it is just a simple wrapper around the contract storage facilities. | ||||||||
|
||||||||
use crate::traits::{ | ||||||||
clear_spread_root, | ||||||||
pull_packed_root_opt, | ||||||||
pull_spread_root, | ||||||||
push_packed_root, | ||||||||
push_spread_root, | ||||||||
ExtKeyPtr, | ||||||||
KeyPtr, | ||||||||
PackedLayout, | ||||||||
SpreadAllocate, | ||||||||
SpreadLayout, | ||||||||
}; | ||||||||
|
||||||||
use core::marker::PhantomData; | ||||||||
|
||||||||
use ink_env::hash::{ | ||||||||
|
@@ -42,7 +41,7 @@ use ink_primitives::Key; | |||||||
#[derive(Default)] | ||||||||
pub struct Mapping<K, V> { | ||||||||
offset_key: Key, | ||||||||
_marker: PhantomData<(K, V)>, | ||||||||
_marker: PhantomData<fn() -> (K, V)>, | ||||||||
} | ||||||||
|
||||||||
impl<K, V> core::fmt::Debug for Mapping<K, V> { | ||||||||
|
@@ -53,52 +52,49 @@ impl<K, V> core::fmt::Debug for Mapping<K, V> { | |||||||
} | ||||||||
} | ||||||||
|
||||||||
impl<K, V> Mapping<K, V> | ||||||||
where | ||||||||
K: PackedLayout, | ||||||||
V: PackedLayout, | ||||||||
{ | ||||||||
impl<K, V> Mapping<K, V> { | ||||||||
/// Creates a new empty `Mapping`. | ||||||||
/// | ||||||||
/// TODO [#961]: Ideally we improve how this is initialized by extending the | ||||||||
/// `SpreadLayout`/`PackedLayout` traits for non-caching data structures. | ||||||||
pub fn new(offset_key: Key) -> Self { | ||||||||
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. | ||||||||
pub fn insert<Q, R>(&mut self, key: &Q, value: &R) | ||||||||
#[inline] | ||||||||
pub fn insert<Q, R>(&mut self, key: Q, value: &R) | ||||||||
where | ||||||||
K: core::borrow::Borrow<Q>, | ||||||||
Q: scale::Encode, | ||||||||
V: core::borrow::Borrow<R>, | ||||||||
R: PackedLayout, | ||||||||
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`. | ||||||||
pub fn get<Q>(&self, key: &Q) -> Option<V> | ||||||||
#[inline] | ||||||||
pub fn get<Q>(&self, key: Q) -> Option<V> | ||||||||
where | ||||||||
K: core::borrow::Borrow<Q>, | ||||||||
Q: scale::Encode, | ||||||||
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 | ||||||||
/// 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 | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. have you benchmarked that this yields an improvement? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we don't need to specify the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just tried now, and there are no changes to the Wasm size. However, there are also no changes if I remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if the compiler also takes into account that passing large types (> 64bit) is very expensive in wasm. Ideally, |
||||||||
where | ||||||||
K: core::borrow::Borrow<Q>, | ||||||||
Q: scale::Encode, | ||||||||
Q: scale::EncodeLike<K>, | ||||||||
{ | ||||||||
let encodedable_key = (&self.offset_key, key); | ||||||||
let mut output = <Blake2x256 as HashOutput>::Type::default(); | ||||||||
|
@@ -113,20 +109,32 @@ impl<K, V> SpreadLayout for Mapping<K, V> { | |||||||
|
||||||||
#[inline] | ||||||||
fn pull_spread(ptr: &mut KeyPtr) -> Self { | ||||||||
let root_key = ExtKeyPtr::next_for::<Self>(ptr); | ||||||||
pull_spread_root::<Self>(root_key) | ||||||||
// 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) { | ||||||||
let root_key = ExtKeyPtr::next_for::<Self>(ptr); | ||||||||
push_spread_root::<Self>(self, root_key); | ||||||||
// 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) { | ||||||||
let root_key = ExtKeyPtr::next_for::<Self>(ptr); | ||||||||
clear_spread_root::<Self>(self, root_key); | ||||||||
// 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)) | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -4,9 +4,14 @@ use ink_lang as ink; | |||||
|
||||||
#[ink::contract] | ||||||
mod erc20 { | ||||||
use ink_primitives::{ | ||||||
Key, | ||||||
KeyPtr, | ||||||
}; | ||||||
use ink_storage::{ | ||||||
collections::mapping::Mapping, | ||||||
lazy::Lazy, | ||||||
traits::SpreadAllocate, | ||||||
}; | ||||||
|
||||||
/// A simple ERC-20 contract. | ||||||
|
@@ -55,24 +60,37 @@ mod erc20 { | |||||
/// The ERC-20 result type. | ||||||
pub type Result<T> = core::result::Result<T, Error>; | ||||||
|
||||||
impl SpreadAllocate for Erc20 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a note here that this is intended to only be temporary and will (hopefully) be replaced by #995? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is better to merge this PR after merging the one PR that improves the SpreadLayout traits. |
||||||
fn allocate_spread(ptr: &mut KeyPtr) -> Self { | ||||||
Self { | ||||||
total_supply: SpreadAllocate::allocate_spread(ptr), | ||||||
balances: SpreadAllocate::allocate_spread(ptr), | ||||||
allowances: SpreadAllocate::allocate_spread(ptr), | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
impl Erc20 { | ||||||
/// Creates a new ERC-20 contract with the specified initial supply. | ||||||
#[ink(constructor)] | ||||||
pub fn new(initial_supply: Balance) -> Self { | ||||||
let root_key = Key::from([0x00; 32]); | ||||||
let mut key_ptr = KeyPtr::from(root_key); | ||||||
let mut instance = Self::allocate_spread(&mut key_ptr); | ||||||
instance.new_init(initial_supply); | ||||||
instance | ||||||
} | ||||||
|
||||||
/// Default initializes the ERC-20 contract with the specified initial supply. | ||||||
fn new_init(&mut self, initial_supply: Balance) { | ||||||
HCastano marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
let caller = Self::env().caller(); | ||||||
let mut balances = Mapping::new([1u8; 32].into()); | ||||||
balances.insert(&caller, &initial_supply); | ||||||
let instance = Self { | ||||||
total_supply: Lazy::new(initial_supply), | ||||||
balances, | ||||||
allowances: Mapping::new([1u8; 32].into()), | ||||||
}; | ||||||
self.balances.insert(&caller, &initial_supply); | ||||||
Lazy::set(&mut self.total_supply, initial_supply); | ||||||
Self::env().emit_event(Transfer { | ||||||
from: None, | ||||||
to: Some(caller), | ||||||
value: initial_supply, | ||||||
}); | ||||||
instance | ||||||
} | ||||||
|
||||||
/// Returns the total token supply. | ||||||
|
@@ -86,15 +104,41 @@ mod erc20 { | |||||
/// Returns `0` if the account is non-existent. | ||||||
#[ink(message)] | ||||||
pub fn balance_of(&self, owner: AccountId) -> Balance { | ||||||
HCastano marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
self.balances.get(&owner).unwrap_or_default() // .copied().unwrap_or(0) | ||||||
self.balance_of_impl(&owner) | ||||||
} | ||||||
|
||||||
/// Returns the account balance for the specified `owner`. | ||||||
/// | ||||||
/// Returns `0` if the account is non-existent. | ||||||
/// | ||||||
/// # Note | ||||||
/// | ||||||
/// Prefer to call this method over `balance_of` since this | ||||||
/// works using references which are more efficient in Wasm. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder whether that's another |
||||||
#[inline] | ||||||
fn balance_of_impl(&self, owner: &AccountId) -> Balance { | ||||||
self.balances.get(owner).unwrap_or_default() | ||||||
} | ||||||
|
||||||
/// Returns the amount which `spender` is still allowed to withdraw from `owner`. | ||||||
/// | ||||||
/// Returns `0` if no allowance has been set `0`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
#[ink(message)] | ||||||
pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance { | ||||||
self.allowances.get(&(owner, spender)).unwrap_or_default() //.copied().unwrap_or(0) | ||||||
self.allowance_impl(&owner, &spender) | ||||||
} | ||||||
|
||||||
/// Returns the amount which `spender` is still allowed to withdraw from `owner`. | ||||||
/// | ||||||
/// Returns `0` if no allowance has been set `0`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
/// | ||||||
/// # Note | ||||||
/// | ||||||
/// Prefer to call this method over `allowance` since this | ||||||
/// works using references which are more efficient in Wasm. | ||||||
#[inline] | ||||||
fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance { | ||||||
self.allowances.get((owner, spender)).unwrap_or_default() | ||||||
HCastano marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
/// Transfers `value` amount of tokens from the caller's account to account `to`. | ||||||
|
@@ -108,7 +152,7 @@ mod erc20 { | |||||
#[ink(message)] | ||||||
pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> { | ||||||
let from = self.env().caller(); | ||||||
self.transfer_from_to(from, to, value) | ||||||
self.transfer_from_to(&from, &to, value) | ||||||
} | ||||||
|
||||||
/// Allows `spender` to withdraw from the caller's account multiple times, up to | ||||||
|
@@ -120,7 +164,7 @@ mod erc20 { | |||||
#[ink(message)] | ||||||
pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { | ||||||
let owner = self.env().caller(); | ||||||
self.allowances.insert(&(owner, spender), &value); | ||||||
self.allowances.insert((&owner, &spender), &value); | ||||||
self.env().emit_event(Approval { | ||||||
owner, | ||||||
spender, | ||||||
|
@@ -151,12 +195,13 @@ mod erc20 { | |||||
value: Balance, | ||||||
) -> Result<()> { | ||||||
let caller = self.env().caller(); | ||||||
let allowance = self.allowance(from, caller); | ||||||
let allowance = self.allowance_impl(&from, &caller); | ||||||
if allowance < value { | ||||||
return Err(Error::InsufficientAllowance) | ||||||
} | ||||||
self.transfer_from_to(from, to, value)?; | ||||||
self.allowances.insert(&(from, caller), &(allowance - value)); | ||||||
self.transfer_from_to(&from, &to, value)?; | ||||||
self.allowances | ||||||
.insert((&from, &caller), &(allowance - value)); | ||||||
Ok(()) | ||||||
} | ||||||
|
||||||
|
@@ -170,20 +215,21 @@ mod erc20 { | |||||
/// the caller's account balance. | ||||||
fn transfer_from_to( | ||||||
&mut self, | ||||||
from: AccountId, | ||||||
to: AccountId, | ||||||
from: &AccountId, | ||||||
to: &AccountId, | ||||||
value: Balance, | ||||||
) -> Result<()> { | ||||||
let from_balance = self.balance_of(from); | ||||||
let from_balance = self.balance_of_impl(from); | ||||||
if from_balance < value { | ||||||
return Err(Error::InsufficientBalance) | ||||||
} | ||||||
self.balances.insert(&from, &(from_balance - value)); | ||||||
let to_balance = self.balance_of(to); | ||||||
self.balances.insert(&to, &(to_balance + value)); | ||||||
|
||||||
self.balances.insert(from, &(from_balance - value)); | ||||||
let to_balance = self.balance_of_impl(to); | ||||||
self.balances.insert(to, &(to_balance + value)); | ||||||
self.env().emit_event(Transfer { | ||||||
from: Some(from), | ||||||
to: Some(to), | ||||||
from: Some(*from), | ||||||
to: Some(*to), | ||||||
value, | ||||||
}); | ||||||
Ok(()) | ||||||
|
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.
Can I think of
EncodeLike
sort-of likeEncodeLike = Borrow + Encode
?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.
EncodeLike is a purely marker trait coming from the parity-scale-codec crate. Please go to the crates docs to read more about its general use.
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.
I read the docs, and I think my question is still valid 😄
I think the semantics of a type being able to be SCALE encoded as another type (
EncodeLike
) vs. being able to be borrowed as another type (Borrow
) are slightly different, which is why I asked