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

Add Mapping::contains(key) and Mapping::insert_return_size(key, val) #1224

Merged
merged 14 commits into from
May 3, 2022
12 changes: 5 additions & 7 deletions crates/engine/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,20 +228,18 @@ impl Engine {
}

/// Writes the encoded value into the storage at the given key.
pub fn set_storage(&mut self, key: &[u8; 32], encoded_value: &[u8]) {
/// Returns the size of the previously stored value at the key if any, or None.
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
pub fn set_storage(&mut self, key: &[u8; 32], encoded_value: &[u8]) -> Option<u32> {
let callee = self.get_callee();
let account_id = AccountId::from_bytes(&callee[..]);

self.debug_info.inc_writes(account_id.clone());
self.debug_info
.record_cell_for_account(account_id, key.to_vec());

// We ignore if storage is already set for this key
let _ = self.database.insert_into_contract_storage(
&callee,
key,
encoded_value.to_vec(),
);
self.database
.insert_into_contract_storage(&callee, key, encoded_value.to_vec())
.map(|v| <u32>::try_from(v.len()).expect("usize to u32 conversion failed"))
}

/// Returns the decoded contract storage at the key if any.
Expand Down
15 changes: 13 additions & 2 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,13 @@ where
})
}

/// Writes the value to the contract storage under the given key.
/// Writes the value to the contract storage under the given key and returns
/// the size of pre-existing value at the specified key if any, or None.
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Panics
///
/// - If the encode length of value exceeds the configured maximum value length of a storage entry.
pub fn set_contract_storage<V>(key: &Key, value: &V)
pub fn set_contract_storage<V>(key: &Key, value: &V) -> Option<u32>
where
V: scale::Encode,
{
Expand All @@ -211,6 +212,16 @@ where
})
}

/// Checks whether there is a value stored under the given key in
/// the contract's storage.
///
/// Size of a value stored under the specified key is returned if any.
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
pub fn contract_storage_contains(key: &Key) -> Option<u32> {
<EnvInstance as OnInstance>::on_instance(|instance| {
EnvBackend::contract_storage_contains(instance, key)
})
}

/// Clears the contract's storage key entry.
pub fn clear_contract_storage(key: &Key) {
<EnvInstance as OnInstance>::on_instance(|instance| {
Expand Down
8 changes: 6 additions & 2 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ impl CallFlags {

/// Environmental contract functionality that does not require `Environment`.
pub trait EnvBackend {
/// Writes the value to the contract storage under the given key.
fn set_contract_storage<V>(&mut self, key: &Key, value: &V)
/// Writes the value to the contract storage under the given key and returns
/// the size of the pre-existing value at the specified key if any, or None.
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
fn set_contract_storage<V>(&mut self, key: &Key, value: &V) -> Option<u32>
where
V: scale::Encode;

Expand All @@ -176,6 +177,9 @@ pub trait EnvBackend {
where
R: scale::Decode;

/// Returns the size of a value stored under the specified key is returned if any.
fn contract_storage_contains(&mut self, key: &Key) -> Option<u32>;

/// Clears the contract's storage key entry.
fn clear_contract_storage(&mut self, key: &Key);

Expand Down
10 changes: 8 additions & 2 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,12 @@ impl EnvInstance {
}

impl EnvBackend for EnvInstance {
fn set_contract_storage<V>(&mut self, key: &Key, value: &V)
fn set_contract_storage<V>(&mut self, key: &Key, value: &V) -> Option<u32>
where
V: scale::Encode,
{
let v = scale::Encode::encode(value);
self.engine.set_storage(key.as_ref(), &v[..]);
self.engine.set_storage(key.as_ref(), &v[..])
}

fn get_contract_storage<R>(&mut self, key: &Key) -> Result<Option<R>>
Expand All @@ -206,6 +206,12 @@ impl EnvBackend for EnvInstance {
Ok(Some(decoded))
}

fn contract_storage_contains(&mut self, _key: &Key) -> Option<u32> {
unimplemented!(
"the off-chain env does not implement `seal_contains_storage`, yet"
)
}

fn clear_contract_storage(&mut self, key: &Key) {
self.engine.clear_storage(key.as_ref())
}
Expand Down
39 changes: 31 additions & 8 deletions crates/env/src/engine/on_chain/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ where
}
}

/// Used as a sentinel value when reading and writing contract memory.
///
/// It is usually used to signal `None` to a contract when only a primitive is allowed
/// and we don't want to go through encoding a full Rust type. Using `u32::Max` is a safe
/// sentinel because contracts are never allowed to use such a large amount of resources
/// that this value makes sense for a memory location or length.
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
const SENTINEL: u32 = u32::MAX;

/// The raw return code returned by the host side.
#[repr(transparent)]
pub struct ReturnCode(u32);
Expand All @@ -190,6 +198,11 @@ impl ReturnCode {
pub fn into_bool(self) -> bool {
self.0.ne(&0)
}
/// Returns Some(val) for underlying `u32` val if it is less than SENTINEL.
/// Otherwise returns None.
pub fn into_option_u32(self) -> Option<u32> {
(self.0 < SENTINEL).then(|| self.0)
}
athei marked this conversation as resolved.
Show resolved Hide resolved
}

type Result = core::result::Result<(), Error>;
Expand Down Expand Up @@ -217,16 +230,12 @@ mod sys {
data_len: u32,
);

pub fn seal_set_storage(
key_ptr: Ptr32<[u8]>,
value_ptr: Ptr32<[u8]>,
value_len: u32,
);
pub fn seal_get_storage(
key_ptr: Ptr32<[u8]>,
output_ptr: Ptr32Mut<[u8]>,
output_len_ptr: Ptr32Mut<u32>,
) -> ReturnCode;

pub fn seal_clear_storage(key_ptr: Ptr32<[u8]>);

pub fn seal_call_chain_extension(
Expand Down Expand Up @@ -360,6 +369,14 @@ mod sys {
output_ptr: Ptr32Mut<[u8]>,
output_len_ptr: Ptr32Mut<u32>,
);

pub fn seal_contains_storage(key_ptr: Ptr32<[u8]>) -> ReturnCode;

pub fn seal_set_storage(
key_ptr: Ptr32<[u8]>,
value_ptr: Ptr32<[u8]>,
value_len: u32,
) -> ReturnCode;
}
}

Expand Down Expand Up @@ -475,14 +492,15 @@ pub fn deposit_event(topics: &[u8], data: &[u8]) {
}
}

pub fn set_storage(key: &[u8], encoded_value: &[u8]) {
unsafe {
pub fn set_storage(key: &[u8], encoded_value: &[u8]) -> Option<u32> {
let ret_code = unsafe {
sys::seal_set_storage(
Ptr32::from_slice(key),
Ptr32::from_slice(encoded_value),
encoded_value.len() as u32,
)
}
};
ret_code.into_option_u32()
}

pub fn clear_storage(key: &[u8]) {
Expand All @@ -504,6 +522,11 @@ pub fn get_storage(key: &[u8], output: &mut &mut [u8]) -> Result {
ret_code.into()
}

pub fn storage_contains(key: &[u8]) -> Option<u32> {
let ret_code = unsafe { sys::seal_contains_storage(Ptr32::from_slice(key)) };
ret_code.into_option_u32()
}

pub fn terminate(beneficiary: &[u8]) -> ! {
unsafe { sys::seal_terminate(Ptr32::from_slice(beneficiary)) }
}
Expand Down
8 changes: 6 additions & 2 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,12 @@ impl EnvInstance {
}

impl EnvBackend for EnvInstance {
fn set_contract_storage<V>(&mut self, key: &Key, value: &V)
fn set_contract_storage<V>(&mut self, key: &Key, value: &V) -> Option<u32>
where
V: scale::Encode,
{
let buffer = self.scoped_buffer().take_encoded(value);
ext::set_storage(key.as_ref(), buffer);
ext::set_storage(key.as_ref(), buffer)
}

fn get_contract_storage<R>(&mut self, key: &Key) -> Result<Option<R>>
Expand All @@ -241,6 +241,10 @@ impl EnvBackend for EnvInstance {
Ok(Some(decoded))
}

fn contract_storage_contains(&mut self, key: &Key) -> Option<u32> {
ext::storage_contains(key.as_ref())
}

fn clear_contract_storage(&mut self, key: &Key) {
ext::clear_storage(key.as_ref())
}
Expand Down
22 changes: 22 additions & 0 deletions crates/storage/src/lazy/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,17 @@ where
push_packed_root(value, &self.storage_key(&key));
}

/// Insert the given `value` to the contract storage.
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
/// Returns the size of the pre-existing value at the specified key if any, or None.
agryaznov marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn insert_return_size<Q, R>(&mut self, key: Q, value: &R) -> Option<u32>
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`.
Expand All @@ -145,6 +156,17 @@ where
pull_packed_root_opt(&self.storage_key(&key))
}

/// Get the size of a value stored at `key` in the contract storage.
///
/// Returns `None` if no `value` exists at the given `key`.
#[inline]
pub fn contains<Q>(&self, key: Q) -> Option<u32>
where
Q: scale::EncodeLike<K>,
{
ink_env::contract_storage_contains(&self.storage_key(&key))
}

/// Clears the value at `key` from storage.
pub fn remove<Q>(&self, key: Q)
where
Expand Down
2 changes: 1 addition & 1 deletion crates/storage/src/traits/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ pub fn forward_push_packed<T>(entity: &T, ptr: &mut KeyPtr)
where
T: PackedLayout,
{
push_packed_root::<T>(entity, ptr.next_for::<T>())
push_packed_root::<T>(entity, ptr.next_for::<T>());
}

/// Clears an instance of type `T` in packed fashion from the contract storage.
Expand Down
4 changes: 2 additions & 2 deletions crates/storage/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,12 @@ where
/// packed layout.
/// - Users should prefer using this function directly instead of using the
/// trait methods on [`PackedLayout`].
pub fn push_packed_root<T>(entity: &T, root_key: &Key)
pub fn push_packed_root<T>(entity: &T, root_key: &Key) -> Option<u32>
where
T: PackedLayout,
{
<T as PackedLayout>::push_packed(entity, root_key);
ink_env::set_contract_storage(root_key, entity);
ink_env::set_contract_storage(root_key, entity)
}

/// Clears the entity from the contract storage using packed layout.
Expand Down
2 changes: 1 addition & 1 deletion crates/storage/src/traits/optspec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ where
//
// Sadly this does not work well with `Option<Option<T>>`.
// For this we'd need specialization in Rust or similar.
super::push_packed_root(value, root_key)
super::push_packed_root(value, root_key);
}
None => {
// Clear the associated storage cell since the entity is `None`.
Expand Down