存储数组
在Starknet上,复杂值(例如元组或结构体)存储在以该存储变量地址开头的连续段中。复杂存储值的大小有 256 个元素的限制,这意味着要在存储中存储超过 255 个元素的数组,我们需要将其分割为大小 n <= 255
的段,并将这些段存储在多个存储地址中。目前 Cairo 没有原生支持存储数组,所以你需要为你希望存储的数组类型实现自己的 Store
特性。
注:虽然在存储中保存数组是可行的,但并不总是推荐这么做,因为读写操作的成本可能非常高。例如,读取一个大小为 n 的数组需要进行
n
次存储读取,而向一个大小为n
的数组写入需要进行n
次存储写入。如果你只需要一次访问数组中的一个元素,建议使用LegacyMap
并在另一个变量中存储数组长度。
以下示例展示了如何为 Array<felt252>
类型实现一个简单的 StorageAccess
特性,使我们能够存储多达 255 个 felt252
元素的数组。
impl StoreFelt252Array of Store<Array<felt252>> {
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<Array<felt252>> {
StoreFelt252Array::read_at_offset(address_domain, base, 0)
}
fn write(
address_domain: u32, base: StorageBaseAddress, value: Array<felt252>
) -> SyscallResult<()> {
StoreFelt252Array::write_at_offset(address_domain, base, 0, value)
}
fn read_at_offset(
address_domain: u32, base: StorageBaseAddress, mut offset: u8
) -> SyscallResult<Array<felt252>> {
let mut arr: Array<felt252> = ArrayTrait::new();
// Read the stored array's length. If the length is superior to 255, the read will fail.
let len: u8 = Store::<u8>::read_at_offset(address_domain, base, offset)
.expect('Storage Span too large');
offset += 1;
// Sequentially read all stored elements and append them to the array.
let exit = len + offset;
loop {
if offset >= exit {
break;
}
let value = Store::<felt252>::read_at_offset(address_domain, base, offset).unwrap();
arr.append(value);
offset += Store::<felt252>::size();
};
// Return the array.
Result::Ok(arr)
}
fn write_at_offset(
address_domain: u32, base: StorageBaseAddress, mut offset: u8, mut value: Array<felt252>
) -> SyscallResult<()> {
// // Store the length of the array in the first storage slot.
let len: u8 = value.len().try_into().expect('Storage - Span too large');
Store::<u8>::write_at_offset(address_domain, base, offset, len);
offset += 1;
// Store the array elements sequentially
loop {
match value.pop_front() {
Option::Some(element) => {
Store::<felt252>::write_at_offset(address_domain, base, offset, element);
offset += Store::<felt252>::size();
},
Option::None(_) => { break Result::Ok(()); }
};
}
}
fn size() -> u8 {
255 * Store::<felt252>::size()
}
}
您可以在合约中导入上面的实现方式,并使用它来在存储中存储数组:
#[starknet::interface]
trait IStoreArrayContract<TContractState> {
fn store_array(ref self: TContractState, arr: Array<felt252>);
fn read_array(self: @TContractState) -> Array<felt252>;
}
#[starknet::contract]
mod StoreArrayContract {
use super::StoreFelt252Array;
#[storage]
struct Storage {
arr: Array<felt252>
}
#[abi(embed_v0)]
impl StoreArrayImpl of super::IStoreArrayContract<ContractState> {
fn store_array(ref self: ContractState, arr: Array<felt252>) {
self.arr.write(arr);
}
fn read_array(self: @ContractState) -> Array<felt252> {
self.arr.read()
}
}
}