Use checked math when calculating storage size (#7885)

This commit is contained in:
Alexander Theißen
2021-01-14 13:44:42 +01:00
committed by GitHub
parent 65569620c2
commit c2ebcae0a6
5 changed files with 46 additions and 41 deletions
+16 -20
View File
@@ -24,7 +24,7 @@ use sp_core::crypto::UncheckedFrom;
use sp_std::prelude::*;
use sp_runtime::traits::{Bounded, Zero, Convert, Saturating};
use frame_support::{
dispatch::DispatchError,
dispatch::DispatchResult,
traits::{ExistenceRequirement, Currency, Time, Randomness},
weights::Weight,
ensure, StorageMap,
@@ -65,7 +65,7 @@ pub trait Ext {
/// Sets the storage entry by the given key to the specified value. If `value` is `None` then
/// the storage entry is deleted.
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>);
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult;
/// Instantiate a contract from the given code.
///
@@ -85,7 +85,7 @@ pub trait Ext {
&mut self,
to: &AccountIdOf<Self::T>,
value: BalanceOf<Self::T>,
) -> Result<(), DispatchError>;
) -> DispatchResult;
/// Transfer all funds to `beneficiary` and delete the contract.
///
@@ -97,7 +97,7 @@ pub trait Ext {
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<(), DispatchError>;
) -> DispatchResult;
/// Call (possibly transferring some amount of funds) into the specified account.
fn call(
@@ -121,7 +121,7 @@ pub trait Ext {
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(), DispatchError>;
) -> DispatchResult;
/// Returns a reference to the account id of the caller.
fn caller(&self) -> &AccountIdOf<Self::T>;
@@ -454,7 +454,7 @@ fn transfer<'a, T: Config, V: Vm<T>, L: Loader<T>>(
dest: &T::AccountId,
value: BalanceOf<T>,
ctx: &mut ExecutionContext<'a, T, V, L>,
) -> Result<(), DispatchError>
) -> DispatchResult
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
@@ -520,23 +520,19 @@ where
Storage::<T>::read(trie_id, key)
}
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) {
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult {
let trie_id = self.ctx.self_trie_id.as_ref().expect(
"`ctx.self_trie_id` points to an alive contract within the `CallContext`;\
it cannot be `None`;\
expect can't fail;\
qed",
);
if let Err(storage::ContractAbsentError) =
Storage::<T>::write(&self.ctx.self_account, trie_id, &key, value)
{
panic!(
"the contract must be in the alive state within the `CallContext`;\
the contract cannot be absent in storage;
write cannot return `None`;
qed"
);
}
// write panics if the passed account is not alive.
// the contract must be in the alive state within the `CallContext`;\
// the contract cannot be absent in storage;
// write cannot return `None`;
// qed
Storage::<T>::write(&self.ctx.self_account, trie_id, &key, value)
}
fn instantiate(
@@ -554,7 +550,7 @@ where
&mut self,
to: &T::AccountId,
value: BalanceOf<T>,
) -> Result<(), DispatchError> {
) -> DispatchResult {
transfer(
TransferCause::Call,
TransactorKind::Contract,
@@ -568,7 +564,7 @@ where
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<(), DispatchError> {
) -> DispatchResult {
let self_id = self.ctx.self_account.clone();
let value = T::Currency::free_balance(&self_id);
if let Some(caller_ctx) = self.ctx.caller {
@@ -612,7 +608,7 @@ where
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(), DispatchError> {
) -> DispatchResult {
if let Some(caller_ctx) = self.ctx.caller {
if caller_ctx.is_live(&self.ctx.self_account) {
return Err(Error::<T>::ReentranceDenied.into());
+5
View File
@@ -421,6 +421,11 @@ decl_error! {
/// This can be returned from [`Module::claim_surcharge`] because the target
/// contract has enough balance to pay for its rent.
ContractNotEvictable,
/// A storage modification exhausted the 32bit type that holds the storage size.
///
/// This can either happen when the accumulated storage in bytes is too large or
/// when number of storage items is too large.
StorageExhausted,
}
}
+13 -8
View File
@@ -49,8 +49,6 @@ pub struct DeletedContract {
trie_id: TrieId,
}
pub struct Storage<T>(PhantomData<T>);
impl<T> Storage<T>
@@ -75,15 +73,19 @@ where
/// `read`, this function also requires the `account` ID.
///
/// If the contract specified by the id `account` doesn't exist `Err` is returned.`
///
/// # Panics
///
/// Panics iff the `account` specified is not alive and in storage.
pub fn write(
account: &AccountIdOf<T>,
trie_id: &TrieId,
key: &StorageKey,
opt_new_value: Option<Vec<u8>>,
) -> Result<(), ContractAbsentError> {
) -> DispatchResult {
let mut new_info = match <ContractInfoOf<T>>::get(account) {
Some(ContractInfo::Alive(alive)) => alive,
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAbsentError),
None | Some(ContractInfo::Tombstone(_)) => panic!("Contract not found"),
};
let hashed_key = blake2_256(key);
@@ -94,10 +96,12 @@ where
// Update the total number of KV pairs and the number of empty pairs.
match (&opt_prev_len, &opt_new_value) {
(Some(_), None) => {
new_info.pair_count -= 1;
new_info.pair_count = new_info.pair_count.checked_sub(1)
.ok_or_else(|| Error::<T>::StorageExhausted)?;
},
(None, Some(_)) => {
new_info.pair_count += 1;
new_info.pair_count = new_info.pair_count.checked_add(1)
.ok_or_else(|| Error::<T>::StorageExhausted)?;
},
(Some(_), Some(_)) => {},
(None, None) => {},
@@ -111,8 +115,9 @@ where
.unwrap_or(0);
new_info.storage_size = new_info
.storage_size
.saturating_add(new_value_len)
.saturating_sub(prev_value_len);
.checked_sub(prev_value_len)
.and_then(|val| val.checked_add(new_value_len))
.ok_or_else(|| Error::<T>::StorageExhausted)?;
new_info.last_write = Some(<frame_system::Module<T>>::block_number());
<ContractInfoOf<T>>::insert(&account, ContractInfo::Alive(new_info));
+10 -9
View File
@@ -18,15 +18,15 @@
//! This module provides a means for executing contracts
//! represented in wasm.
use crate::{CodeHash, Schedule, Config};
use crate::wasm::env_def::FunctionImplProvider;
use crate::exec::Ext;
use crate::gas::GasMeter;
use crate::{
CodeHash, Schedule, Config,
wasm::env_def::FunctionImplProvider,
exec::Ext,
gas::GasMeter,
};
use sp_std::prelude::*;
use sp_core::crypto::UncheckedFrom;
use codec::{Encode, Decode};
use sp_sandbox;
#[macro_use]
mod env_def;
@@ -172,7 +172,7 @@ mod tests {
use sp_core::H256;
use hex_literal::hex;
use sp_runtime::DispatchError;
use frame_support::weights::Weight;
use frame_support::{dispatch::DispatchResult, weights::Weight};
use assert_matches::assert_matches;
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags, ExecError, ErrorOrigin};
@@ -228,8 +228,9 @@ mod tests {
fn get_storage(&self, key: &StorageKey) -> Option<Vec<u8>> {
self.storage.get(key).cloned()
}
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) {
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult {
*self.storage.entry(key).or_insert(Vec::new()) = value.unwrap_or(Vec::new());
Ok(())
}
fn instantiate(
&mut self,
@@ -362,7 +363,7 @@ mod tests {
fn get_storage(&self, key: &[u8; 32]) -> Option<Vec<u8>> {
(**self).get_storage(key)
}
fn set_storage(&mut self, key: [u8; 32], value: Option<Vec<u8>>) {
fn set_storage(&mut self, key: [u8; 32], value: Option<Vec<u8>>) -> DispatchResult {
(**self).set_storage(key, value)
}
fn instantiate(
@@ -643,8 +643,7 @@ define_env!(Env, <E: Ext>,
let mut key: StorageKey = [0; 32];
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
let value = Some(ctx.read_sandbox_memory(value_ptr, value_len)?);
ctx.ext.set_storage(key, value);
Ok(())
ctx.ext.set_storage(key, value).map_err(Into::into)
},
// Clear the value at the given key in the contract storage.
@@ -656,8 +655,7 @@ define_env!(Env, <E: Ext>,
ctx.charge_gas(RuntimeToken::ClearStorage)?;
let mut key: StorageKey = [0; 32];
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
ctx.ext.set_storage(key, None);
Ok(())
ctx.ext.set_storage(key, None).map_err(Into::into)
},
// Retrieve the value under the given key from storage.