Commit d92462b3 authored by Michael Müller's avatar Michael Müller Committed by Hero Bird

Deprecate CellChunkAlloc for DynAlloc (#186)

* Replace CellChunkAlloc with DynAlloc

* Remove CellChunkAlloc

* Satisfy linter

* Replace CellChunkAlloc with DynAlloc

* Remove CellChunkAlloc

* Satisfy linter

* Do not initialize env for each call

* Fix typo

* Remove tests which panick while panicking

These tests test for panicks. The issue is that
the `Drop` implementation for `BitVec` will be
called during the unwinding, this `Drop`
implementation will try to access uninitialized
fields and panick as well.
parent 53fa4d58
Pipeline #52956 failed with stages
......@@ -28,7 +28,7 @@ use type_metadata::Metadata;
/// It is not designed to be used during contract execution and it
/// also cannot deallocate key allocated by it.
///
/// Users are recommended to use the [`CellChunkAlloc`](struct.CellChunkAlloc.html)
/// Users are recommended to use the [`DynAlloc`](struct.DynAlloc.html)
/// for dynamic storage allocation purposes instead.
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
pub struct BumpAlloc {
......
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::storage::{
self,
Flush,
Key,
};
#[cfg(feature = "ink-generate-abi")]
use ink_abi::{
HasLayout,
LayoutField,
LayoutStruct,
StorageLayout,
};
use scale::{
Decode,
Encode,
};
#[cfg(feature = "ink-generate-abi")]
use type_metadata::Metadata;
/// An allocator for the contract storage.
///
/// Specialized to efficiently allocate and deallocate cells and chunks.
///
/// # Note
///
/// This allocator allows for two types of allocations:
///
/// 1. Single cell allocation
/// 2. Cell chunk allocation (2^32 cells)
///
/// Allocating and deallocating are always O(1) operations.
#[derive(Debug, Encode, Decode)]
#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
pub struct CellChunkAlloc {
/// Allocator stash for single cells.
cells: storage::Stash<()>,
/// Allocator stash for cell chunks.
chunks: storage::Stash<()>,
/// Cells key offset.
cells_off: Key,
/// Chunks key offset.
chunks_off: Key,
}
impl AllocateUsing for CellChunkAlloc {
/// Creates a new cell & chunks allocator using the given allocator.
///
/// The cell & chunks allocator is the default allocator for dynamic
/// storage allocations that may be required by some smart contracts
/// during smart contract execution.
///
/// # Note
///
/// At first it might seem strange to allocate one allocator with another.
/// Normally [`CellChunkAlloc`](struct.CellChunkAlloc.html) should be allocated
/// using a [`BumpAlloc`](struct.BumpAlloc.html).
/// The [`BumpAlloc`](struct.BumpAlloc.html) itself cannot be stored in the
/// contract storage and should also not be used for dynamic storage
/// allocations since it panics upon deallocation.
///
/// Store your only instance of the cell & chunks allocator on the storage
/// once upon deployment of your contract and reuse that instance for
/// all consecutive executions.
unsafe fn allocate_using<A>(alloc: &mut A) -> Self
where
A: Allocate,
{
Self {
cells: AllocateUsing::allocate_using(alloc),
chunks: AllocateUsing::allocate_using(alloc),
cells_off: alloc.alloc(u32::max_value().into()),
chunks_off: alloc.alloc(u32::max_value().into()),
}
}
}
impl Initialize for CellChunkAlloc {
type Args = ();
fn initialize(&mut self, _args: Self::Args) {
self.cells.initialize(());
self.chunks.initialize(());
}
}
impl Flush for CellChunkAlloc {
fn flush(&mut self) {
self.cells.flush();
self.chunks.flush();
}
}
#[cfg(feature = "ink-generate-abi")]
impl HasLayout for CellChunkAlloc {
fn layout(&self) -> StorageLayout {
LayoutStruct::new(
Self::meta_type(),
vec![
LayoutField::of("cells", &self.cells),
LayoutField::of("chunks", &self.chunks),
],
)
.into()
}
}
impl CellChunkAlloc {
/// Returns the key to the first cell allocation.
///
/// # Note
///
/// This key is then used to determine the key for every
/// other cell allocation using its allocation index.
pub(crate) fn cells_offset_key(&self) -> Key {
self.cells_off
}
/// Returns the key to the first chunk allocation.
///
/// # Note
///
/// This key is then used to determine the key for every
/// other chunk allocation using its allocation index.
pub(crate) fn chunks_offset_key(&self) -> Key {
self.chunks_off
}
/// Allocates a new storage region that fits for a single cell.
fn alloc_cell(&mut self) -> Key {
let index = self.cells.put(());
self.cell_index_to_key(index)
}
/// Allocates a new storage region that fits for a whole chunk.
fn alloc_chunk(&mut self) -> Key {
let index = self.chunks.put(());
self.chunk_index_to_key(index)
}
/// Deallocates a storage region fit for a single cell.
fn dealloc_cell(&mut self, key: Key) {
let index = self.key_to_cell_index(key);
self.cells.take(index).expect(
"[ink_core::CellChunkAlloc::dealloc_cell] Error: \
key was not allocated by the allocator",
)
}
/// Deallocates a storage region fit for a whole chunk.
fn dealloc_chunk(&mut self, key: Key) {
let index = self.key_to_chunk_index(key);
self.chunks.take(index).expect(
"[ink_core::CellChunkAlloc::dealloc_chunk] Error: \
key was not allocated by the allocator",
)
}
/// Converts cell indices to keys.
///
/// The reverse of `key_to_cell_index`.
fn cell_index_to_key(&self, index: u32) -> Key {
self.cells_offset_key() + index
}
/// Converts keys to cell indices.
///
/// The reverse of `cell_index_to_key`.
fn key_to_cell_index(&self, key: Key) -> u32 {
let diff = key - self.cells_offset_key();
diff.try_to_u32().expect(
"if allocated by this allocator the difference between
the given key and offset key must be less-than or equal
to u32::MAX.",
)
}
/// Converts chunk indices to keys.
///
/// The reverse of `key_to_chunk_index`.
fn chunk_index_to_key(&self, index: u32) -> Key {
let chunk_offset: u64 = (1 << 32) * u64::from(index);
self.chunks_offset_key() + chunk_offset
}
/// Converts keys to chunk indices.
///
/// The reverse of `chunk_index_to_key`.
fn key_to_chunk_index(&self, key: Key) -> u32 {
let diff = key - self.cells_offset_key();
let index = diff.try_to_u64().expect(
"if allocated by this allocator the difference between
the given key and offset key must be less-than or equal
to u64::MAX.",
);
(index >> 32) as u32
}
}
impl Allocate for CellChunkAlloc {
/// Can only allocate sizes of up to `u32::MAX`.
fn alloc(&mut self, size: u64) -> Key {
assert!(size <= u64::from(u32::max_value()));
debug_assert!(size != 0);
if size == 1 {
self.alloc_cell()
} else {
self.alloc_chunk()
}
}
}
impl Allocator for CellChunkAlloc {
fn dealloc(&mut self, key: Key) {
// This assumes that the given key was previously
// generated by the associated call to `Allocator::alloc`
// of this same allocator implementor.
assert!(key >= self.cells_offset_key());
// This condition requires cells offset key
// to be always smaller than chunks offset key.
//
// This must either be an invariant or we need
// another more safe condition in the future.
if key < self.chunks_offset_key() {
// The key was allocated as a cell
self.dealloc_cell(key)
} else {
// The key was allocated as a chunk
self.dealloc_chunk(key)
}
}
}
......@@ -17,7 +17,6 @@
//! Facilities to allocate and deallocate contract storage dynamically.
mod bump_alloc;
mod cc_alloc;
mod dyn_alloc;
mod traits;
......@@ -26,7 +25,6 @@ mod tests;
pub use self::{
bump_alloc::BumpAlloc,
cc_alloc::CellChunkAlloc,
dyn_alloc::DynAlloc,
traits::{
Allocate,
......
......@@ -21,62 +21,6 @@ use crate::{
test_utils::run_test,
};
#[test]
fn cc_simple() {
run_test(|| {
use crate::storage;
let mut alloc = unsafe {
let mut fw_alloc = storage::alloc::BumpAlloc::from_raw_parts(Key([0x0; 32]));
let mut cc_alloc =
storage::alloc::CellChunkAlloc::allocate_using(&mut fw_alloc);
cc_alloc.initialize(());
cc_alloc
};
let cells_entries = alloc.cells_offset_key();
let chunks_entries = alloc.chunks_offset_key();
let mut cell_allocs = [Key([0; 32]); 5];
let mut chunk_allocs = [Key([0; 32]); 5];
// Cell allocations
for i in 0..5 {
cell_allocs[i] = alloc.alloc(1);
assert_eq!(cell_allocs[i], cells_entries + (i as u32));
}
// Chunk allocations
let alloc_sizes = &[10, u32::max_value() as u64, 1337, 2, 9999_9999];
for (i, &size) in alloc_sizes.into_iter().enumerate() {
chunk_allocs[i] = alloc.alloc(size);
assert_eq!(chunk_allocs[i], chunks_entries + ((1 << 32) * (i as u64)));
}
// Deallocate first cell again
alloc.dealloc(cell_allocs[0]);
// Now the next cell allocation will take the first allocation cell again
assert_eq!(alloc.alloc(1), cell_allocs[0]);
// Deallocate 2nd and 4th allocations in reverse order
alloc.dealloc(cell_allocs[3]);
alloc.dealloc(cell_allocs[1]);
assert_eq!(alloc.alloc(1), cell_allocs[1]);
assert_eq!(alloc.alloc(1), cell_allocs[3]);
// Deallocate first chunk again
alloc.dealloc(chunk_allocs[0]);
// Now the next chunk allocation will take the first allocation cell again
assert_eq!(alloc.alloc(u32::max_value() as u64), chunk_allocs[0]);
// Deallocate 2nd and 4th allocations in reverse order
alloc.dealloc(chunk_allocs[3]);
alloc.dealloc(chunk_allocs[1]);
assert_eq!(alloc.alloc(u32::max_value() as u64), chunk_allocs[1]);
assert_eq!(alloc.alloc(u32::max_value() as u64), chunk_allocs[3]);
})
}
#[test]
fn dyn_simple() {
run_test(|| {
......
......@@ -253,7 +253,7 @@ where
{
/// Creates an instance of the contract declaration.
///
/// This assocates the state with the contract storage
/// This associates the state with the contract storage
/// and defines its layout.
pub fn instantiate(self) -> ContractInstance<State, Env, DeployArgs, HandlerChain> {
use ink_core::storage::{
......@@ -263,7 +263,7 @@ where
},
Key,
};
let env: ExecutionEnv<State, Env> = unsafe {
let env = unsafe {
// Note that it is totally fine here to start with a key
// offset of `0x0` as long as we only consider having one
// contract instance per execution. Otherwise their
......
......@@ -25,7 +25,7 @@ use ink_core::{
storage::alloc::{
Allocate,
AllocateUsing,
CellChunkAlloc,
DynAlloc,
Initialize,
},
};
......@@ -110,7 +110,7 @@ impl<State, Env> ExecutionEnv<State, Env> {
/// allocations and deallocations.
pub struct EnvHandler<T> {
/// The dynamic allocator.
pub dyn_alloc: CellChunkAlloc,
pub dyn_alloc: DynAlloc,
env_marker: PhantomData<T>,
}
......
......@@ -68,17 +68,3 @@ fn inc_and_read() {
contract.call::<Inc>(41);
assert_eq!(contract.call::<Get>(()), 42_u32);
}
#[test]
#[should_panic]
fn read_without_deploy() {
let mut contract = instantiate();
let _res = contract.call::<Get>(());
}
#[test]
#[should_panic]
fn write_without_deploy() {
let mut contract = instantiate();
contract.call::<Inc>(100);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment