Skip to content

Commit

Permalink
Parametrizing SDK Storage by a Host
Browse files Browse the repository at this point in the history
  • Loading branch information
rauljordan committed Dec 27, 2024
1 parent d02336b commit cbca272
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 138 deletions.
6 changes: 3 additions & 3 deletions stylus-proc/src/macros/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn top_level_storage_impl(item: &syn::ItemStruct) -> syn::ItemImpl {

fn struct_entrypoint_fn(name: &Ident) -> syn::ItemFn {
parse_quote! {
fn #STRUCT_ENTRYPOINT_FN<H: Host>(input: alloc::vec::Vec<u8>, host: Rc<H>) -> stylus_sdk::ArbResult {
fn #STRUCT_ENTRYPOINT_FN<H: Host>(input: alloc::vec::Vec<u8>, host: &H) -> stylus_sdk::ArbResult {
stylus_sdk::abi::router_entrypoint::<#name<H>, #name<H>, H>(input, host)
}
}
Expand Down Expand Up @@ -155,12 +155,12 @@ fn user_entrypoint_fn(user_fn: Ident) -> syn::ItemFn {
parse_quote! {
#[no_mangle]
pub extern "C" fn user_entrypoint(len: usize) -> usize {
let host = Rc::new(WasmHost{});
let host = WasmHost{};
#deny_reentrant
host.pay_for_memory_grow(0);

let input = host.args(len);
let (data, status) = match #user_fn(input, Rc::clone(&host)) {
let (data, status) = match #user_fn(input, &host) {
Ok(data) => (data, 0),
Err(data) => (data, 1),
};
Expand Down
24 changes: 20 additions & 4 deletions stylus-proc/src/macros/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl Storage {
const SLOT_BYTES: usize = 32;
const REQUIRED_SLOTS: usize = Self::required_slots();

unsafe fn new(mut root: stylus_sdk::alloy_primitives::U256, offset: u8, host: Rc<H>) -> Self {
unsafe fn new(mut root: stylus_sdk::alloy_primitives::U256, offset: u8, host: *const H) -> Self {
use stylus_sdk::{storage, alloy_primitives};
debug_assert!(offset == 0);

Expand All @@ -96,6 +96,20 @@ impl Storage {
}
}
}

fn impl_host_access(&self) -> syn::ItemImpl {
let name = &self.name;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
parse_quote! {
impl #impl_generics stylus_sdk::host::HostAccess<H> for #name #ty_generics #where_clause {
fn get_host(&self) -> &H {
// SAFETY: Host is guaranteed to be valid and non-null for the lifetime of the storage
// as injected by the Stylus entrypoint function.
unsafe { &*self.host }
}
}
}
}
}

impl From<&mut syn::ItemStruct> for Storage {
Expand Down Expand Up @@ -126,6 +140,7 @@ impl ToTokens for Storage {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.item_impl().to_tokens(tokens);
self.impl_storage_type().to_tokens(tokens);
self.impl_host_access().to_tokens(tokens);
for field in &self.fields {
field.impl_borrow(&self.name).to_tokens(tokens);
field.impl_borrow_mut(&self.name).to_tokens(tokens);
Expand Down Expand Up @@ -166,7 +181,7 @@ impl StorageField {
return None;
};
let ty = &self.ty;
if ty.to_token_stream().to_string() == "Rc < H >".to_string() {
if ty.to_token_stream().to_string() == "*const H".to_string() {

Check warning on line 184 in stylus-proc/src/macros/storage.rs

View workflow job for this annotation

GitHub Actions / clippy

this creates an owned instance just for comparison

warning: this creates an owned instance just for comparison --> stylus-proc/src/macros/storage.rs:184:48 | 184 | if ty.to_token_stream().to_string() == "*const H".to_string() { | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `*"*const H"` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned = note: `#[warn(clippy::cmp_owned)]` on by default

Check warning on line 184 in stylus-proc/src/macros/storage.rs

View workflow job for this annotation

GitHub Actions / clippy

this creates an owned instance just for comparison

warning: this creates an owned instance just for comparison --> stylus-proc/src/macros/storage.rs:184:48 | 184 | if ty.to_token_stream().to_string() == "*const H".to_string() { | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `*"*const H"` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned = note: `#[warn(clippy::cmp_owned)]` on by default
return None;
}
Some(parse_quote! {
Expand All @@ -180,7 +195,7 @@ impl StorageField {
space -= bytes;

let root = root + alloy_primitives::U256::from(slot);
let field = <#ty as storage::StorageType<H>>::new(root, space as u8, Rc::clone(&host));
let field = <#ty as storage::StorageType<H>>::new(root, space as u8, host);
if words > 0 {
slot += words;
space = 32;
Expand All @@ -192,7 +207,8 @@ impl StorageField {

fn size(&self) -> TokenStream {
let ty = &self.ty;
if ty.to_token_stream().to_string() == "Rc < H >".to_string() {
println!("{}", ty.to_token_stream().to_string());

Check warning on line 210 in stylus-proc/src/macros/storage.rs

View workflow job for this annotation

GitHub Actions / clippy

`to_string` applied to a type that implements `Display` in `println!` args

warning: `to_string` applied to a type that implements `Display` in `println!` args --> stylus-proc/src/macros/storage.rs:210:44 | 210 | println!("{}", ty.to_token_stream().to_string()); | ^^^^^^^^^^^^ help: remove this | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args = note: `#[warn(clippy::to_string_in_format_args)]` on by default

Check warning on line 210 in stylus-proc/src/macros/storage.rs

View workflow job for this annotation

GitHub Actions / clippy

`to_string` applied to a type that implements `Display` in `println!` args

warning: `to_string` applied to a type that implements `Display` in `println!` args --> stylus-proc/src/macros/storage.rs:210:44 | 210 | println!("{}", ty.to_token_stream().to_string()); | ^^^^^^^^^^^^ help: remove this | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args = note: `#[warn(clippy::to_string_in_format_args)]` on by default
if ty.to_token_stream().to_string() == "*const H".to_string() {

Check warning on line 211 in stylus-proc/src/macros/storage.rs

View workflow job for this annotation

GitHub Actions / clippy

this creates an owned instance just for comparison

warning: this creates an owned instance just for comparison --> stylus-proc/src/macros/storage.rs:211:48 | 211 | if ty.to_token_stream().to_string() == "*const H".to_string() { | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `*"*const H"` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned

Check warning on line 211 in stylus-proc/src/macros/storage.rs

View workflow job for this annotation

GitHub Actions / clippy

this creates an owned instance just for comparison

warning: this creates an owned instance just for comparison --> stylus-proc/src/macros/storage.rs:211:48 | 211 | if ty.to_token_stream().to_string() == "*const H".to_string() { | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `*"*const H"` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
return quote! {};
}
quote! {
Expand Down
3 changes: 1 addition & 2 deletions stylus-sdk/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use alloc::vec::Vec;
use alloy_primitives::U256;
use core::borrow::BorrowMut;
use rclite::Rc;

use alloy_sol_types::{abi::TokenSeq, private::SolTypeValue, SolType};

Expand Down Expand Up @@ -98,7 +97,7 @@ where
// if no value is received in the transaction. It is implicitly payable.
// - Fallback is called when no other function matches a selector. If a receive function is not
// defined, then calls with no input calldata will be routed to the fallback function.
pub fn router_entrypoint<R, S, H>(input: alloc::vec::Vec<u8>, host: Rc<H>) -> ArbResult
pub fn router_entrypoint<R, S, H>(input: alloc::vec::Vec<u8>, host: &H) -> ArbResult
where
R: Router<S>,
S: StorageType<H> + TopLevelStorage + BorrowMut<R::Storage>,
Expand Down
4 changes: 4 additions & 0 deletions stylus-sdk/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub trait Host:
{
}

pub trait HostAccess<H: Host> {

Check warning on line 30 in stylus-sdk/src/host/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

missing documentation for a trait

warning: missing documentation for a trait --> stylus-sdk/src/host/mod.rs:30:1 | 30 | pub trait HostAccess<H: Host> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> stylus-sdk/src/lib.rs:27:9 | 27 | #![warn(missing_docs)] | ^^^^^^^^^^^^
fn get_host(&self) -> &H;

Check warning on line 31 in stylus-sdk/src/host/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

missing documentation for a method

warning: missing documentation for a method --> stylus-sdk/src/host/mod.rs:31:5 | 31 | fn get_host(&self) -> &H; | ^^^^^^^^^^^^^^^^^^^^^^^^^
}

/// TODO
pub trait CryptographyAccess {
/// TODO
Expand Down
19 changes: 13 additions & 6 deletions stylus-sdk/src/storage/array.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
// Copyright 2023-2024, Offchain Labs, Inc.
// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md

use crate::host::Host;
use crate::host::{Host, HostAccess};

use super::{Erase, StorageGuard, StorageGuardMut, StorageType};
use alloy_primitives::U256;
use core::marker::PhantomData;
use rclite::Rc;

/// Accessor for a storage-backed array.
pub struct StorageArray<H: Host, S: StorageType<H>, const N: usize> {
slot: U256,
marker: PhantomData<S>,
host: Rc<H>,
host: *const H,
}

impl<H: Host, S: StorageType<H>, const N: usize> StorageType<H> for StorageArray<H, S, N> {
Expand All @@ -27,7 +26,7 @@ impl<H: Host, S: StorageType<H>, const N: usize> StorageType<H> for StorageArray

const REQUIRED_SLOTS: usize = Self::required_slots();

unsafe fn new(slot: U256, offset: u8, host: Rc<H>) -> Self {
unsafe fn new(slot: U256, offset: u8, host: *const H) -> Self {
debug_assert!(offset == 0);
Self {
slot,
Expand All @@ -45,6 +44,14 @@ impl<H: Host, S: StorageType<H>, const N: usize> StorageType<H> for StorageArray
}
}

impl<H: Host, S: StorageType<H>, const N: usize> HostAccess<H> for StorageArray<H, S, N> {
fn get_host(&self) -> &H {
// SAFETY: Host is guaranteed to be valid and non-null for the lifetime of the storage
// as injected by the Stylus entrypoint function.
unsafe { &*self.host }
}
}

impl<H: Host, S: StorageType<H>, const N: usize> StorageArray<H, S, N> {
/// Gets the number of elements stored.
///
Expand Down Expand Up @@ -82,7 +89,7 @@ impl<H: Host, S: StorageType<H>, const N: usize> StorageArray<H, S, N> {
return None;
}
let (slot, offset) = self.index_slot(index);
Some(S::new(slot, offset, Rc::clone(&self.host)))
Some(S::new(slot, offset, self.get_host()))
}

/// Gets the underlying accessor to the element at a given index, even if out of bounds.
Expand All @@ -92,7 +99,7 @@ impl<H: Host, S: StorageType<H>, const N: usize> StorageArray<H, S, N> {
/// 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, Rc::clone(&self.host))
S::new(slot, offset, self.get_host())
}

/// Gets the element at the given index, if it exists.
Expand Down
60 changes: 33 additions & 27 deletions stylus-sdk/src/storage/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md

use super::{Erase, GlobalStorage, Storage, StorageB8, StorageGuard, StorageGuardMut, StorageType};
use crate::{crypto, host::Host};
use crate::{
crypto,
host::{Host, HostAccess},
};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use alloy_primitives::{U256, U8};
use core::cell::OnceCell;
use rclite::Rc;

/// Accessor for storage-backed bytes.
pub struct StorageBytes<H: Host> {
root: U256,
base: OnceCell<U256>,
host: Rc<H>,
host: *const H,
}

impl<H: Host> StorageType<H> for StorageBytes<H> {
Expand All @@ -28,7 +30,7 @@ impl<H: Host> StorageType<H> for StorageBytes<H> {
where
Self: 'a;

unsafe fn new(root: U256, offset: u8, host: Rc<H>) -> Self {
unsafe fn new(root: U256, offset: u8, host: *const H) -> Self {
debug_assert!(offset == 0);
Self {
root,
Expand All @@ -46,6 +48,14 @@ impl<H: Host> StorageType<H> for StorageBytes<H> {
}
}

impl<H: Host> HostAccess<H> for StorageBytes<H> {
fn get_host(&self) -> &H {
// SAFETY: Host is guaranteed to be valid and non-null for the lifetime of the storage
// as injected by the Stylus entrypoint function.
unsafe { &*self.host }
}
}

impl<H: Host> StorageBytes<H> {
/// Returns `true` if the collection contains no elements.
pub fn is_empty(&self) -> bool {
Expand All @@ -54,7 +64,7 @@ impl<H: Host> StorageBytes<H> {

/// Gets the number of bytes stored.
pub fn len(&self) -> usize {
let word = Storage::get_word(Rc::clone(&self.host), self.root);
let word = Storage::get_word(self.get_host(), self.root);

// check if the data is short
let slot: &[u8] = word.as_ref();
Expand Down Expand Up @@ -83,30 +93,26 @@ impl<H: Host> StorageBytes<H> {

// if shrinking, pull data in
if (len < 32) && (old > 32) {
let word = Storage::get_word(Rc::clone(&self.host), *self.base());
Storage::set_word(Rc::clone(&self.host), self.root, word);
let word = Storage::get_word(self.get_host(), *self.base());
Storage::set_word(self.get_host(), self.root, word);
return self.write_len(len);
}

// if growing, push data out
let mut word = Storage::get_word(Rc::clone(&self.host), self.root);
let mut word = Storage::get_word(self.get_host(), self.root);
word[31] = 0; // clear len byte
Storage::set_word(Rc::clone(&self.host), *self.base(), word);
Storage::set_word(self.get_host(), *self.base(), word);
self.write_len(len)
}

/// Updates the length while being conscious of representation.
unsafe fn write_len(&mut self, len: usize) {
if len < 32 {
// place the len in the last byte of the root with the long bit low
Storage::set_uint(Rc::clone(&self.host), self.root, 31, U8::from(len * 2));
Storage::set_uint(self.get_host(), self.root, 31, U8::from(len * 2));
} else {
// place the len in the root with the long bit high
Storage::set_word(
Rc::clone(&self.host),
self.root,
U256::from(len * 2 + 1).into(),
)
Storage::set_word(self.get_host(), self.root, U256::from(len * 2 + 1).into())
}
}

Expand All @@ -118,7 +124,7 @@ impl<H: Host> StorageBytes<H> {
macro_rules! assign {
($slot:expr) => {
unsafe {
Storage::set_uint(Rc::clone(&self.host), $slot, index % 32, value); // pack value
Storage::set_uint(self.get_host(), $slot, index % 32, value); // pack value
self.write_len(index + 1);
}
};
Expand All @@ -131,8 +137,8 @@ impl<H: Host> StorageBytes<H> {
// convert to multi-word representation
if index == 31 {
// copy content over (len byte will be overwritten)
let word = Storage::get_word(Rc::clone(&self.host), self.root);
unsafe { Storage::set_word(Rc::clone(&self.host), *self.base(), word) };
let word = Storage::get_word(self.get_host(), self.root);
unsafe { Storage::set_word(self.get_host(), *self.base(), word) };
}

let slot = self.base() + U256::from(index / 32);
Expand All @@ -152,13 +158,13 @@ impl<H: Host> StorageBytes<H> {
let clean = index % 32 == 0;
let byte = self.get(index)?;

let clear = |slot| unsafe { Storage::clear_word(Rc::clone(&self.host), slot) };
let clear = |slot| unsafe { Storage::clear_word(self.get_host(), slot) };

// convert to single-word representation
if len == 32 {
// copy content over
let word = Storage::get_word(Rc::clone(&self.host), *self.base());
unsafe { Storage::set_word(Rc::clone(&self.host), self.root, word) };
let word = Storage::get_word(self.get_host(), *self.base());
unsafe { Storage::set_word(self.get_host(), self.root, word) };
clear(*self.base());
}

Expand All @@ -169,7 +175,7 @@ impl<H: Host> StorageBytes<H> {

// clear the value
if len < 32 {
unsafe { Storage::set_byte(Rc::clone(&self.host), self.root, index, 0) };
unsafe { Storage::set_byte(self.get_host(), self.root, index, 0) };
}

// set the new length
Expand All @@ -193,7 +199,7 @@ impl<H: Host> StorageBytes<H> {
return None;
}
let (slot, offset) = self.index_slot(index);
let value = unsafe { StorageB8::new(slot, offset, Rc::clone(&self.host)) };
let value = unsafe { StorageB8::new(slot, offset, self.get_host()) };
Some(StorageGuardMut::new(value))
}

Expand All @@ -204,7 +210,7 @@ impl<H: Host> StorageBytes<H> {
/// UB if index is out of bounds.
pub unsafe fn get_unchecked(&self, index: usize) -> u8 {
let (slot, offset) = self.index_slot(index);
unsafe { Storage::get_byte(Rc::clone(&self.host), slot, offset.into()) }
unsafe { Storage::get_byte(self.get_host(), slot, offset.into()) }
}

/// Gets the full contents of the collection.
Expand Down Expand Up @@ -247,11 +253,11 @@ impl<H: Host> Erase<H> for StorageBytes<H> {
if len > 31 {
while len > 0 {
let slot = self.index_slot(len as usize - 1).0;
unsafe { Storage::clear_word(Rc::clone(&self.host), slot) };
unsafe { Storage::clear_word(self.get_host(), slot) };
len -= 32;
}
}
unsafe { Storage::clear_word(Rc::clone(&self.host), self.root) };
unsafe { Storage::clear_word(self.get_host(), self.root) };
}
}

Expand Down Expand Up @@ -284,7 +290,7 @@ impl<H: Host> StorageType<H> for StorageString<H> {
where
Self: 'a;

unsafe fn new(slot: U256, offset: u8, host: Rc<H>) -> Self {
unsafe fn new(slot: U256, offset: u8, host: *const H) -> Self {
Self(StorageBytes::new(slot, offset, host))
}

Expand Down
Loading

0 comments on commit cbca272

Please sign in to comment.