generated from eigerco/beerus
-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add store implementation for bytes type; includes tests
- Loading branch information
1 parent
bbc010b
commit 39ccd99
Showing
5 changed files
with
222 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
mod bytes; | ||
mod storage; | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
mod utils; | ||
|
||
use bytes::{Bytes, BytesTrait}; | ||
use storage::BytesStore; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
use alexandria_bytes::bytes::{Bytes, BytesTrait, BYTES_PER_ELEMENT}; | ||
use starknet::SyscallResult; | ||
use starknet::storage_access::{ | ||
Store, StorageAddress, StorageBaseAddress, storage_address_from_base, | ||
storage_base_address_from_felt252, storage_address_from_base_and_offset | ||
}; | ||
|
||
/// Store for a `Bytes` object. | ||
/// | ||
/// The layout of a `Bytes` object in storage is as follows: | ||
/// * Only the size in bytes is stored in the original address where the | ||
/// bytes object is stored. | ||
/// * The actual data is stored in chunks of 256 `u128` values in another location | ||
/// in storage determined by the hash of: | ||
/// - The address storing the size of the bytes object. | ||
/// - The chunk index. | ||
/// - The short string `Bytes`. | ||
impl BytesStore of Store<Bytes> { | ||
#[inline(always)] | ||
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<Bytes> { | ||
inner_read_bytes(address_domain, storage_address_from_base(base)) | ||
} | ||
#[inline(always)] | ||
fn write(address_domain: u32, base: StorageBaseAddress, value: Bytes) -> SyscallResult<()> { | ||
inner_write_bytes(address_domain, storage_address_from_base(base), value) | ||
} | ||
#[inline(always)] | ||
fn read_at_offset( | ||
address_domain: u32, base: StorageBaseAddress, offset: u8 | ||
) -> SyscallResult<Bytes> { | ||
inner_read_bytes(address_domain, storage_address_from_base_and_offset(base, offset)) | ||
} | ||
#[inline(always)] | ||
fn write_at_offset( | ||
address_domain: u32, base: StorageBaseAddress, offset: u8, value: Bytes | ||
) -> SyscallResult<()> { | ||
inner_write_bytes(address_domain, storage_address_from_base_and_offset(base, offset), value) | ||
} | ||
#[inline(always)] | ||
fn size() -> u8 { | ||
1 | ||
} | ||
} | ||
|
||
/// Returns a pointer to the `chunk`'th of the Bytes object at `address`. | ||
/// The pointer is the `Poseidon` hash of: | ||
/// * `address` - The address of the Bytes object (where the size is stored). | ||
/// * `chunk` - The index of the chunk. | ||
/// * The short string `Bytes` is used as the capacity argument of the sponge | ||
/// construction (domain separation). | ||
fn inner_bytes_pointer(address: StorageAddress, chunk: felt252) -> StorageBaseAddress { | ||
let (r, _, _) = core::poseidon::hades_permutation(address.into(), chunk, 'Bytes'_felt252); | ||
storage_base_address_from_felt252(r) | ||
} | ||
|
||
/// Reads a bytes from storage from domain `address_domain` and address `address`. | ||
/// The length of the bytes is read from `address` at domain `address_domain`. | ||
/// For more info read the documentation of `BytesStore`. | ||
fn inner_read_bytes(address_domain: u32, address: StorageAddress) -> SyscallResult<Bytes> { | ||
let size: usize = | ||
match starknet::syscalls::storage_read_syscall(address_domain, address)?.try_into() { | ||
Option::Some(x) => x, | ||
Option::None => { return SyscallResult::Err(array!['Invalid Bytes size']); }, | ||
}; | ||
let (mut remaining_full_words, last_word_len) = core::DivRem::div_rem( | ||
size, BYTES_PER_ELEMENT.try_into().unwrap() | ||
); | ||
let mut chunk = 0; | ||
let mut chunk_base = inner_bytes_pointer(address, chunk); | ||
let mut index_in_chunk = 0_u8; | ||
let mut result: Bytes = BytesTrait::new_empty(); | ||
loop { | ||
if remaining_full_words == 0 { | ||
break Result::Ok(()); | ||
} | ||
let value = | ||
match starknet::syscalls::storage_read_syscall( | ||
address_domain, storage_address_from_base_and_offset(chunk_base, index_in_chunk) | ||
) { | ||
Result::Ok(value) => value, | ||
Result::Err(err) => { break Result::Err(err); }, | ||
}; | ||
let value: u128 = match value.try_into() { | ||
Option::Some(x) => x, | ||
Option::None => { break Result::Err(array!['Invalid inner value']); }, | ||
}; | ||
result.data.append(value); | ||
remaining_full_words -= 1; | ||
index_in_chunk = match core::integer::u8_overflowing_add(index_in_chunk, 1) { | ||
Result::Ok(x) => x, | ||
Result::Err(_) => { | ||
// After reading 256 `uint128`s `index_in_chunk` will overflow and we move to the | ||
// next chunk. | ||
chunk += 1; | ||
chunk_base = inner_bytes_pointer(address, chunk); | ||
0 | ||
}, | ||
}; | ||
}?; | ||
if last_word_len != 0 { | ||
let last_word = starknet::syscalls::storage_read_syscall( | ||
address_domain, storage_address_from_base_and_offset(chunk_base, index_in_chunk) | ||
)?; | ||
result.data.append(last_word.try_into().expect('Invalid last word')); | ||
} | ||
result.size = size; | ||
Result::Ok(result) | ||
} | ||
|
||
/// Writes a bytes to storage at domain `address_domain` and address `address`. | ||
/// The length of the bytes is written to `address` at domain `address_domain`. | ||
/// For more info read the documentation of `BytesStore`. | ||
fn inner_write_bytes( | ||
address_domain: u32, address: StorageAddress, value: Bytes | ||
) -> SyscallResult<()> { | ||
let size = value.size; | ||
starknet::syscalls::storage_write_syscall(address_domain, address, size.into())?; | ||
let mut words = value.data.span(); | ||
let mut chunk = 0; | ||
let mut chunk_base = inner_bytes_pointer(address, chunk); | ||
let mut index_in_chunk = 0_u8; | ||
loop { | ||
let curr_value = match words.pop_front() { | ||
Option::Some(x) => x, | ||
Option::None => { break Result::Ok(()); }, | ||
}; | ||
match starknet::syscalls::storage_write_syscall( | ||
address_domain, | ||
storage_address_from_base_and_offset(chunk_base, index_in_chunk), | ||
(*curr_value).into() | ||
) { | ||
Result::Ok(_) => {}, | ||
Result::Err(err) => { break Result::Err(err); }, | ||
}; | ||
index_in_chunk = match core::integer::u8_overflowing_add(index_in_chunk, 1) { | ||
Result::Ok(x) => x, | ||
Result::Err(_) => { | ||
// After writing 256 `uint128`s `index_in_chunk` will overflow and we move to the | ||
// next chunk. | ||
chunk += 1; | ||
chunk_base = inner_bytes_pointer(address, chunk); | ||
0 | ||
}, | ||
}; | ||
}?; | ||
Result::Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
mod test_bytes; | ||
mod test_bytes_store; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
use alexandria_bytes::Bytes; | ||
|
||
#[starknet::interface] | ||
trait IABytesStore<TContractState> { | ||
fn get_bytes(self: @TContractState) -> Bytes; | ||
fn set_bytes(ref self: TContractState, bytes: Bytes); | ||
} | ||
|
||
#[starknet::contract] | ||
mod ABytesStore { | ||
use alexandria_bytes::{Bytes, BytesStore}; | ||
|
||
#[storage] | ||
struct Storage { | ||
bytes: Bytes, | ||
} | ||
|
||
#[abi(embed_v0)] | ||
impl ABytesStoreImpl of super::IABytesStore<ContractState> { | ||
fn get_bytes(self: @ContractState) -> Bytes { | ||
self.bytes.read() | ||
} | ||
|
||
fn set_bytes(ref self: ContractState, bytes: Bytes) { | ||
self.bytes.write(bytes); | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use alexandria_bytes::utils::{BytesDebug, BytesDisplay}; | ||
use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; | ||
use starknet::{ClassHash, ContractAddress, deploy_syscall, SyscallResultTrait,}; | ||
use super::{ABytesStore, IABytesStoreDispatcher, IABytesStoreDispatcherTrait}; | ||
|
||
fn deploy() -> IABytesStoreDispatcher { | ||
let class_hash: ClassHash = ABytesStore::TEST_CLASS_HASH.try_into().unwrap(); | ||
let ctor_data: Array<felt252> = Default::default(); | ||
let (addr, _) = deploy_syscall(class_hash, 0, ctor_data.span(), false).unwrap_syscall(); | ||
IABytesStoreDispatcher { contract_address: addr } | ||
} | ||
|
||
#[test] | ||
fn test_deploy() { | ||
let contract = deploy(); | ||
assert_eq!(contract.get_bytes(), BytesTrait::new_empty(), "Initial bytes should be empty"); | ||
} | ||
|
||
#[test] | ||
fn test_bytes_storage_32_bytes() { | ||
let contract = deploy(); | ||
let bytes = BytesTrait::new(32, array![0x01020304050607080910, 0x11121314151617181920]); | ||
contract.set_bytes(bytes.clone()); | ||
assert_eq!(contract.get_bytes(), bytes, "Bytes should be set correctly"); | ||
} | ||
|
||
#[test] | ||
fn test_bytes_storage_40_bytes() { | ||
let contract = deploy(); | ||
let bytes = BytesTrait::new( | ||
40, | ||
array![ | ||
0x01020304050607080910, 0x11121314151617181920, 0x21222324252627280000000000000000 | ||
] | ||
); | ||
contract.set_bytes(bytes.clone()); | ||
assert_eq!(contract.get_bytes(), bytes, "Bytes should be set correctly"); | ||
} | ||
} |