From 1e06d5f565f9368a6073bf028578309efe4141b9 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 17 Aug 2023 12:23:14 -0400 Subject: [PATCH 1/9] add fixed arrays into the sdk --- stylus-proc/src/storage.rs | 16 ++++++-- stylus-sdk/src/storage/array.rs | 70 +++++++++++++++++++++++++++++++++ stylus-sdk/src/storage/mod.rs | 2 + 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 stylus-sdk/src/storage/array.rs diff --git a/stylus-proc/src/storage.rs b/stylus-proc/src/storage.rs index 6c7ac737..0d898bd9 100644 --- a/stylus-proc/src/storage.rs +++ b/stylus-proc/src/storage.rs @@ -2,7 +2,7 @@ // For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md use lazy_static::lazy_static; -use proc_macro2::Ident; +use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; use regex::Regex; use syn::{ @@ -109,8 +109,18 @@ impl Parse for SolidityTy { }; while input.peek(Bracket) { - let _content; - let _ = bracketed!(_content in input); // TODO: fixed arrays + let content; + let _ = bracketed!(content in input); + if let Ok(bracket_contents) = content.parse::() { + println!("Got a literal"); + // TODO: Check it is an integer. + let size = bracket_contents; + let outer = sdk!("StorageArray"); + let inner = quote! { #path }; + path = syn::parse_str(&format!("{outer}<{inner}, {size}>"))?; + return Ok(SolidityTy(path)); + }; + let outer = sdk!("StorageVec"); let inner = quote! { #path }; path = syn::parse_str(&format!("{outer}<{inner}>"))?; diff --git a/stylus-sdk/src/storage/array.rs b/stylus-sdk/src/storage/array.rs new file mode 100644 index 00000000..8433c223 --- /dev/null +++ b/stylus-sdk/src/storage/array.rs @@ -0,0 +1,70 @@ +// Copyright 2023, Offchain Labs, Inc. +// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md + +use super::{Erase, StorageGuard, StorageGuardMut, StorageType}; +use alloy_primitives::U256; +use std::marker::PhantomData; + +/// Accessor for a storage-backed array. +pub struct StorageArray { + marker: PhantomData, + item_slots: Vec, +} + +impl StorageType for StorageArray { + type Wraps<'a> = StorageGuard<'a, StorageArray> where Self: 'a; + type WrapsMut<'a> = StorageGuardMut<'a, StorageArray> where Self: 'a; + + unsafe fn new(slot: U256, offset: u8) -> Self { + let mut curr_slot = slot; + let mut item_slots = vec![]; + for _ in 0..L { + // TODO: Deal with offsets properly. + let _ = S::new(curr_slot, 0); + curr_slot = curr_slot + alloy_primitives::U256::from(S::REQUIRED_SLOTS); + item_slots.push(curr_slot); + } + debug_assert!(offset == 0); + Self { + marker: PhantomData, + item_slots, + } + } + + fn load<'s>(self) -> Self::Wraps<'s> { + StorageGuard::new(self) + } + + fn load_mut<'s>(self) -> Self::WrapsMut<'s> { + StorageGuardMut::new(self) + } +} + +impl StorageArray { + /// Gets the element at the given index, if it exists. + pub fn get(&self, index: impl TryInto) -> Option> { + // TODO: Check that item exists at index. + let slot = self.item_slots.get(index.try_into().ok()?).unwrap(); + let s = unsafe { S::new(*slot, 0) }; + Some(s.load()) + } + + /// Gets a mutable accessor to the element at a given index, if it exists. + pub fn get_mut(&mut self, index: impl TryInto) -> Option> { + // TODO: Check that item exists at index. + let slot = self.item_slots.get(index.try_into().ok()?).unwrap(); + let s = unsafe { S::new(*slot, 0) }; + Some(s.load_mut()) + } +} + +impl Erase for StorageArray { + fn erase(&mut self) { + for i in 0..L { + // TODO: iter over item slots instead. + let slot = self.item_slots.get(i).unwrap(); + let mut s = unsafe { S::new(*slot, 0) }; + s.erase(); + } + } +} diff --git a/stylus-sdk/src/storage/mod.rs b/stylus-sdk/src/storage/mod.rs index 8aa06952..ae565b5e 100644 --- a/stylus-sdk/src/storage/mod.rs +++ b/stylus-sdk/src/storage/mod.rs @@ -4,6 +4,7 @@ use alloy_primitives::{Address, BlockHash, BlockNumber, FixedBytes, Signed, Uint, U256}; use std::{cell::OnceCell, ops::Deref}; +pub use array::StorageArray; pub use bytes::{StorageBytes, StorageString}; pub use cache::{ Erase, SimpleStorageType, StorageCache, StorageGuard, StorageGuardMut, StorageType, @@ -11,6 +12,7 @@ pub use cache::{ pub use map::StorageMap; pub use vec::StorageVec; +mod array; mod bytes; mod cache; mod map; From c42480033e0c6fcf9e36d9df62579c4efb2a34e7 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 17 Aug 2023 12:36:11 -0400 Subject: [PATCH 2/9] support fixed size arrays in stylus --- stylus-proc/src/storage.rs | 8 ++++---- stylus-sdk/src/storage/array.rs | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/stylus-proc/src/storage.rs b/stylus-proc/src/storage.rs index d2ae30a8..0290ea4c 100644 --- a/stylus-proc/src/storage.rs +++ b/stylus-proc/src/storage.rs @@ -2,7 +2,7 @@ // For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md use lazy_static::lazy_static; -use proc_macro2::{Ident, Literal, TokenStream}; +use proc_macro2::{Ident, Literal}; use quote::quote; use regex::Regex; use syn::{ @@ -113,9 +113,9 @@ impl Parse for SolidityTy { let content; let _ = bracketed!(content in input); if let Ok(bracket_contents) = content.parse::() { - println!("Got a literal"); - // TODO: Check it is an integer. - let size = bracket_contents; + let size = bracket_contents.to_string().parse::().map_err(|_| { + Error::new_spanned(&bracket_contents, "Array size must be a positive integer") + })?; let outer = sdk!("StorageArray"); let inner = quote! { #path }; path = syn::parse_str(&format!("{outer}<{inner}, {size}>"))?; diff --git a/stylus-sdk/src/storage/array.rs b/stylus-sdk/src/storage/array.rs index 8433c223..9d31be1e 100644 --- a/stylus-sdk/src/storage/array.rs +++ b/stylus-sdk/src/storage/array.rs @@ -15,6 +15,9 @@ impl StorageType for StorageArray { type Wraps<'a> = StorageGuard<'a, StorageArray> where Self: 'a; type WrapsMut<'a> = StorageGuardMut<'a, StorageArray> where Self: 'a; + // Must have at least one required slot. + const REQUIRED_SLOTS: usize = 1; + unsafe fn new(slot: U256, offset: u8) -> Self { let mut curr_slot = slot; let mut item_slots = vec![]; @@ -43,16 +46,14 @@ impl StorageType for StorageArray { impl StorageArray { /// Gets the element at the given index, if it exists. pub fn get(&self, index: impl TryInto) -> Option> { - // TODO: Check that item exists at index. - let slot = self.item_slots.get(index.try_into().ok()?).unwrap(); + let slot = self.item_slots.get(index.try_into().ok()?)?; let s = unsafe { S::new(*slot, 0) }; Some(s.load()) } /// Gets a mutable accessor to the element at a given index, if it exists. pub fn get_mut(&mut self, index: impl TryInto) -> Option> { - // TODO: Check that item exists at index. - let slot = self.item_slots.get(index.try_into().ok()?).unwrap(); + let slot = self.item_slots.get(index.try_into().ok()?)?; let s = unsafe { S::new(*slot, 0) }; Some(s.load_mut()) } @@ -60,9 +61,7 @@ impl StorageArray { impl Erase for StorageArray { fn erase(&mut self) { - for i in 0..L { - // TODO: iter over item slots instead. - let slot = self.item_slots.get(i).unwrap(); + for slot in self.item_slots.iter() { let mut s = unsafe { S::new(*slot, 0) }; s.erase(); } From df807f09a6449e60e76602c05954e397a58a9131 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 17 Aug 2023 12:52:41 -0400 Subject: [PATCH 3/9] no need to do s::new --- stylus-sdk/src/storage/array.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stylus-sdk/src/storage/array.rs b/stylus-sdk/src/storage/array.rs index 9d31be1e..22b1b67d 100644 --- a/stylus-sdk/src/storage/array.rs +++ b/stylus-sdk/src/storage/array.rs @@ -18,16 +18,14 @@ impl StorageType for StorageArray { // Must have at least one required slot. const REQUIRED_SLOTS: usize = 1; - unsafe fn new(slot: U256, offset: u8) -> Self { + unsafe fn new(slot: U256, _offset: u8) -> Self { + debug_assert!(L != 0); let mut curr_slot = slot; let mut item_slots = vec![]; for _ in 0..L { - // TODO: Deal with offsets properly. - let _ = S::new(curr_slot, 0); curr_slot = curr_slot + alloy_primitives::U256::from(S::REQUIRED_SLOTS); item_slots.push(curr_slot); } - debug_assert!(offset == 0); Self { marker: PhantomData, item_slots, From 230a7f59cd93b1fc73e99c8e829ba58e2a22bc77 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 17 Aug 2023 14:33:26 -0400 Subject: [PATCH 4/9] stylus storage array --- stylus-sdk/src/storage/array.rs | 113 ++++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 28 deletions(-) diff --git a/stylus-sdk/src/storage/array.rs b/stylus-sdk/src/storage/array.rs index 22b1b67d..c218078c 100644 --- a/stylus-sdk/src/storage/array.rs +++ b/stylus-sdk/src/storage/array.rs @@ -2,33 +2,30 @@ // For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md use super::{Erase, StorageGuard, StorageGuardMut, StorageType}; +use crate::crypto; use alloy_primitives::U256; -use std::marker::PhantomData; +use std::{cell::OnceCell, marker::PhantomData}; /// Accessor for a storage-backed array. -pub struct StorageArray { +pub struct StorageArray { + slot: U256, + base: OnceCell, marker: PhantomData, - item_slots: Vec, } -impl StorageType for StorageArray { - type Wraps<'a> = StorageGuard<'a, StorageArray> where Self: 'a; - type WrapsMut<'a> = StorageGuardMut<'a, StorageArray> where Self: 'a; +impl StorageType for StorageArray { + type Wraps<'a> = StorageGuard<'a, StorageArray> where Self: 'a; + type WrapsMut<'a> = StorageGuardMut<'a, StorageArray> where Self: 'a; - // Must have at least one required slot. - const REQUIRED_SLOTS: usize = 1; + const REQUIRED_SLOTS: usize = N * S::REQUIRED_SLOTS; - unsafe fn new(slot: U256, _offset: u8) -> Self { - debug_assert!(L != 0); - let mut curr_slot = slot; - let mut item_slots = vec![]; - for _ in 0..L { - curr_slot = curr_slot + alloy_primitives::U256::from(S::REQUIRED_SLOTS); - item_slots.push(curr_slot); - } + unsafe fn new(slot: U256, offset: u8) -> Self { + debug_assert!(offset == 0); + debug_assert!(N > 0); Self { + slot, + base: OnceCell::new(), marker: PhantomData, - item_slots, } } @@ -41,27 +38,87 @@ impl StorageType for StorageArray { } } -impl StorageArray { +impl StorageArray { + /// Gets an accessor to the element at a given index, if it exists. + /// Note: the accessor is protected by a [`StorageGuard`], which restricts + /// its lifetime to that of `&self`. + pub fn getter(&self, index: impl TryInto) -> Option> { + let store = unsafe { self.accessor(index)? }; + Some(StorageGuard::new(store)) + } + + /// Gets a mutable accessor to the element at a given index, if it exists. + /// Note: the accessor is protected by a [`StorageGuardMut`], which restricts + /// its lifetime to that of `&mut self`. + pub fn setter(&mut self, index: impl TryInto) -> Option> { + let store = unsafe { self.accessor(index)? }; + Some(StorageGuardMut::new(store)) + } + + /// Gets the underlying accessor to the element at a given index, if it exists. + /// + /// # Safety + /// + /// Enables aliasing. + unsafe fn accessor(&self, index: impl TryInto) -> Option { + let index = index.try_into().ok()?; + if index >= N { + return None; + } + let (slot, offset) = self.index_slot(index); + Some(S::new(slot, offset)) + } + + /// Gets the underlying accessor to the element at a given index, even if out of bounds. + /// + /// # Safety + /// + /// Enables aliasing. UB if out of bounds. + unsafe fn accessor_unchecked(&self, index: usize) -> S { + let (slot, offset) = self.index_slot(index); + S::new(slot, offset) + } + /// Gets the element at the given index, if it exists. pub fn get(&self, index: impl TryInto) -> Option> { - let slot = self.item_slots.get(index.try_into().ok()?)?; - let s = unsafe { S::new(*slot, 0) }; - Some(s.load()) + let store = unsafe { self.accessor(index)? }; + Some(store.load()) } /// Gets a mutable accessor to the element at a given index, if it exists. pub fn get_mut(&mut self, index: impl TryInto) -> Option> { - let slot = self.item_slots.get(index.try_into().ok()?)?; - let s = unsafe { S::new(*slot, 0) }; - Some(s.load_mut()) + let store = unsafe { self.accessor(index)? }; + Some(store.load_mut()) + } + + /// Determines the slot and offset for the element at an index. + fn index_slot(&self, index: usize) -> (U256, u8) { + let width = S::SLOT_BYTES; + let words = S::REQUIRED_SLOTS.max(1); + let density = self.density(); + + let slot = self.base() + U256::from(words * index / density); + let offset = 32 - (width * (1 + index % density)) as u8; + (slot, offset) + } + + /// Number of elements per slot. + const fn density(&self) -> usize { + 32 / S::SLOT_BYTES + } + + /// Determines where in storage indices start. Could be made const in the future. + fn base(&self) -> &U256 { + self.base + .get_or_init(|| crypto::keccak(self.slot.to_be_bytes::<32>()).into()) } } -impl Erase for StorageArray { +impl Erase for StorageArray { fn erase(&mut self) { - for slot in self.item_slots.iter() { - let mut s = unsafe { S::new(*slot, 0) }; - s.erase(); + for i in 0..N { + let mut store = unsafe { self.accessor_unchecked(i) }; + store.erase() } } } From 22546a46b22e5a79ef2ce9be7f3f6096956c456a Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 17 Aug 2023 16:34:50 -0400 Subject: [PATCH 5/9] feedback --- stylus-sdk/src/storage/array.rs | 47 ++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/stylus-sdk/src/storage/array.rs b/stylus-sdk/src/storage/array.rs index c218078c..250ed24c 100644 --- a/stylus-sdk/src/storage/array.rs +++ b/stylus-sdk/src/storage/array.rs @@ -2,14 +2,12 @@ // For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md use super::{Erase, StorageGuard, StorageGuardMut, StorageType}; -use crate::crypto; use alloy_primitives::U256; -use std::{cell::OnceCell, marker::PhantomData}; +use std::marker::PhantomData; /// Accessor for a storage-backed array. pub struct StorageArray { slot: U256, - base: OnceCell, marker: PhantomData, } @@ -17,14 +15,19 @@ impl StorageType for StorageArray { type Wraps<'a> = StorageGuard<'a, StorageArray> where Self: 'a; type WrapsMut<'a> = StorageGuardMut<'a, StorageArray> where Self: 'a; - const REQUIRED_SLOTS: usize = N * S::REQUIRED_SLOTS; + // Because certain types, such as some primitives, can have 0 required slots + // (many of them can fit in a single slot), we need to do a computation + // to figure out how many slots in total the array will take up. + // For example, if we have an element that takes up 8 bytes, and we want + // a fixed array of 10 of these elements, we will need 80 bytes in total, + // which would fit into 3 slots. + const REQUIRED_SLOTS: usize = Self::required_slots(); unsafe fn new(slot: U256, offset: u8) -> Self { debug_assert!(offset == 0); debug_assert!(N > 0); Self { slot, - base: OnceCell::new(), marker: PhantomData, } } @@ -97,7 +100,7 @@ impl StorageArray { let words = S::REQUIRED_SLOTS.max(1); let density = self.density(); - let slot = self.base() + U256::from(words * index / density); + let slot = self.slot + U256::from(words * index / density); let offset = 32 - (width * (1 + index % density)) as u8; (slot, offset) } @@ -107,10 +110,16 @@ impl StorageArray { 32 / S::SLOT_BYTES } - /// Determines where in storage indices start. Could be made const in the future. - fn base(&self) -> &U256 { - self.base - .get_or_init(|| crypto::keccak(self.slot.to_be_bytes::<32>()).into()) + /// Required slots for the storage array. A maximum of either N * S::REQUIRED_SLOTS, + /// or ceil((S::SLOT_BYTES * N) / 32), as there are items that can fit multiple times + /// in a single slot. + const fn required_slots() -> usize { + let left = N * S::REQUIRED_SLOTS; + let right = ceil_div(S::SLOT_BYTES * N, 32); + if left > right { + return left; + } + right } } @@ -122,3 +131,21 @@ impl Erase for StorageArray { } } } + +// Note: b must be non-zero. +const fn ceil_div(a: usize, b: usize) -> usize { + (a + (b - 1)) / b +} + +#[cfg(test)] +mod test { + use super::ceil_div; + + #[test] + fn test_ceil() { + assert_eq!(ceil_div(80, 32), 3); + assert_eq!(ceil_div(1, 1), 1); + assert_eq!(ceil_div(0, 1), 0); + assert_eq!(ceil_div(100, 30), 4); + } +} From 29de623bd86bb49c6a8ca24e3c7d8ec5757aacd4 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 17 Aug 2023 17:02:39 -0400 Subject: [PATCH 6/9] density calc --- stylus-sdk/src/storage/array.rs | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/stylus-sdk/src/storage/array.rs b/stylus-sdk/src/storage/array.rs index 250ed24c..d705c66d 100644 --- a/stylus-sdk/src/storage/array.rs +++ b/stylus-sdk/src/storage/array.rs @@ -15,12 +15,6 @@ impl StorageType for StorageArray { type Wraps<'a> = StorageGuard<'a, StorageArray> where Self: 'a; type WrapsMut<'a> = StorageGuardMut<'a, StorageArray> where Self: 'a; - // Because certain types, such as some primitives, can have 0 required slots - // (many of them can fit in a single slot), we need to do a computation - // to figure out how many slots in total the array will take up. - // For example, if we have an element that takes up 8 bytes, and we want - // a fixed array of 10 of these elements, we will need 80 bytes in total, - // which would fit into 3 slots. const REQUIRED_SLOTS: usize = Self::required_slots(); unsafe fn new(slot: U256, offset: u8) -> Self { @@ -98,7 +92,7 @@ impl StorageArray { fn index_slot(&self, index: usize) -> (U256, u8) { let width = S::SLOT_BYTES; let words = S::REQUIRED_SLOTS.max(1); - let density = self.density(); + let density = Self::density(); let slot = self.slot + U256::from(words * index / density); let offset = 32 - (width * (1 + index % density)) as u8; @@ -106,16 +100,17 @@ impl StorageArray { } /// Number of elements per slot. - const fn density(&self) -> usize { + const fn density() -> usize { 32 / S::SLOT_BYTES } /// Required slots for the storage array. A maximum of either N * S::REQUIRED_SLOTS, - /// or ceil((S::SLOT_BYTES * N) / 32), as there are items that can fit multiple times + /// or ceil(N / density), as there are items that can fit multiple times /// in a single slot. const fn required_slots() -> usize { let left = N * S::REQUIRED_SLOTS; - let right = ceil_div(S::SLOT_BYTES * N, 32); + let density = Self::density(); + let right = (N + density - 1) / density; // ceil division. if left > right { return left; } @@ -131,21 +126,3 @@ impl Erase for StorageArray { } } } - -// Note: b must be non-zero. -const fn ceil_div(a: usize, b: usize) -> usize { - (a + (b - 1)) / b -} - -#[cfg(test)] -mod test { - use super::ceil_div; - - #[test] - fn test_ceil() { - assert_eq!(ceil_div(80, 32), 3); - assert_eq!(ceil_div(1, 1), 1); - assert_eq!(ceil_div(0, 1), 0); - assert_eq!(ceil_div(100, 30), 4); - } -} From 4b896526fc1f03d799ade84b1591f00f4aa13abc Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 17 Aug 2023 17:03:46 -0400 Subject: [PATCH 7/9] eliminate debug assert --- stylus-sdk/src/storage/array.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stylus-sdk/src/storage/array.rs b/stylus-sdk/src/storage/array.rs index d705c66d..5b91d138 100644 --- a/stylus-sdk/src/storage/array.rs +++ b/stylus-sdk/src/storage/array.rs @@ -19,7 +19,6 @@ impl StorageType for StorageArray { unsafe fn new(slot: U256, offset: u8) -> Self { debug_assert!(offset == 0); - debug_assert!(N > 0); Self { slot, marker: PhantomData, From 405cffb63a1637de0d93c5ec51ef19315b3d3d9e Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 5 Sep 2023 18:40:30 -0600 Subject: [PATCH 8/9] recursive fixed array types --- stylus-proc/src/storage/proc.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/stylus-proc/src/storage/proc.rs b/stylus-proc/src/storage/proc.rs index 92b18696..43685b77 100644 --- a/stylus-proc/src/storage/proc.rs +++ b/stylus-proc/src/storage/proc.rs @@ -117,19 +117,20 @@ impl Parse for SolidityTy { while input.peek(Bracket) { let content; let _ = bracketed!(content in input); - if let Ok(bracket_contents) = content.parse::() { - let size = bracket_contents.to_string().parse::().map_err(|_| { - Error::new_spanned(&bracket_contents, "Array size must be a positive integer") - })?; + + if content.is_empty() { + let outer = sdk!("StorageVec"); + let inner = quote! { #path }; + path = syn::parse_str(&format!("{outer}<{inner}>"))?; + } else { + let content: Literal = content.parse()?; + let Ok(size) = content.to_string().parse::() else { + error!(@content, "Array size must be a positive integer"); + }; let outer = sdk!("StorageArray"); let inner = quote! { #path }; path = syn::parse_str(&format!("{outer}<{inner}, {size}>"))?; - return Ok(SolidityTy(path)); - }; - - let outer = sdk!("StorageVec"); - let inner = quote! { #path }; - path = syn::parse_str(&format!("{outer}<{inner}>"))?; + } } Ok(SolidityTy(path)) From 1398a1b4a636801347876cf222cdc1d69c529556 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Tue, 5 Sep 2023 20:15:20 -0600 Subject: [PATCH 9/9] tweak docstring --- stylus-sdk/src/storage/array.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/stylus-sdk/src/storage/array.rs b/stylus-sdk/src/storage/array.rs index 5b91d138..074da031 100644 --- a/stylus-sdk/src/storage/array.rs +++ b/stylus-sdk/src/storage/array.rs @@ -103,17 +103,15 @@ impl StorageArray { 32 / S::SLOT_BYTES } - /// Required slots for the storage array. A maximum of either N * S::REQUIRED_SLOTS, - /// or ceil(N / density), as there are items that can fit multiple times - /// in a single slot. + /// Required slots for the storage array. const fn required_slots() -> usize { - let left = N * S::REQUIRED_SLOTS; + let reserved = N * S::REQUIRED_SLOTS; let density = Self::density(); - let right = (N + density - 1) / density; // ceil division. - if left > right { - return left; + let packed = (N + density - 1) / density; // ceil division for packed items. + if reserved > packed { + return reserved; } - right + packed } }