Skip to content

Commit cea2b82

Browse files
authored
feat: Storable (#149)
1 parent 0a71c94 commit cea2b82

File tree

3 files changed

+255
-1
lines changed

3 files changed

+255
-1
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
mod test_iterable_map;
2+
mod test_utils;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use starknet::EthAddress;
2+
use starkware_utils::storage::utils::{Castable160, Castable64};
3+
4+
#[test]
5+
fn test_u8_castable() {
6+
let val: u8 = 10;
7+
let packed = Castable160::encode(val);
8+
let unpacked: u8 = Castable160::decode(packed);
9+
assert(val == unpacked, 'u8 mismatch');
10+
}
11+
12+
#[test]
13+
fn test_u128_castable() {
14+
let val: u128 = 340282366920938463463374607431768211455; // MAX
15+
let packed = Castable160::encode(val);
16+
let unpacked: u128 = Castable160::decode(packed);
17+
assert(val == unpacked, 'u128 mismatch');
18+
}
19+
20+
#[test]
21+
fn test_i8_castable() {
22+
let val: i8 = -10;
23+
let packed = Castable160::encode(val);
24+
let unpacked: i8 = Castable160::decode(packed);
25+
assert(val == unpacked, 'i8 mismatch');
26+
27+
let val: i8 = 127;
28+
let packed = Castable160::encode(val);
29+
let unpacked: i8 = Castable160::decode(packed);
30+
assert(val == unpacked, 'i8 max mismatch');
31+
32+
let val: i8 = -128;
33+
let packed = Castable160::encode(val);
34+
let unpacked: i8 = Castable160::decode(packed);
35+
assert(val == unpacked, 'i8 min mismatch');
36+
}
37+
38+
#[test]
39+
fn test_i128_castable() {
40+
let val: i128 = -170141183460469231731687303715884105728;
41+
let packed = Castable160::encode(val);
42+
let unpacked: i128 = Castable160::decode(packed);
43+
assert(val == unpacked, 'i128 mismatch');
44+
}
45+
46+
#[test]
47+
fn test_eth_address_castable() {
48+
let val: EthAddress = 1252485858049991401322336118102073441816987242963.try_into().unwrap();
49+
let packed = Castable160::encode(val);
50+
let unpacked: EthAddress = Castable160::decode(packed);
51+
assert(val == unpacked, 'EthAddress mismatch');
52+
}
53+
54+
#[test]
55+
#[should_panic(expected: ('Castable160: high bits not 0',))]
56+
fn test_high_bits_panic() {
57+
let packed = (1, 1); // High bit set
58+
let _val: u8 = Castable160::decode(packed);
59+
}
60+
61+
#[test]
62+
fn test_u8_Castable64() {
63+
let val: u8 = 10;
64+
let packed = Castable64::encode(val);
65+
let unpacked: u8 = Castable64::decode(packed);
66+
assert(val == unpacked, 'u8 Castable64 mismatch');
67+
}
68+
69+
#[test]
70+
fn test_u64_Castable64() {
71+
let val: u64 = 18446744073709551615; // MAX
72+
let packed = Castable64::encode(val);
73+
let unpacked: u64 = Castable64::decode(packed);
74+
assert(val == unpacked, 'u64 Castable64 mismatch');
75+
}
76+
77+
#[test]
78+
fn test_i8_Castable64() {
79+
let val: i8 = -10;
80+
let packed = Castable64::encode(val);
81+
let unpacked: i8 = Castable64::decode(packed);
82+
assert(val == unpacked, 'i8 Castable64 mismatch');
83+
}
84+
85+
#[test]
86+
fn test_i64_Castable64() {
87+
let val: i64 = -10;
88+
let packed = Castable64::encode(val);
89+
let unpacked: i64 = Castable64::decode(packed);
90+
assert(val == unpacked, 'i64 Castable64 mismatch');
91+
92+
let val: i64 = 9223372036854775807; // MAX
93+
let packed = Castable64::encode(val);
94+
let unpacked: i64 = Castable64::decode(packed);
95+
assert(val == unpacked, 'i64 max Castable64 mismatch');
96+
97+
let val: i64 = -9223372036854775808; // MIN
98+
let packed = Castable64::encode(val);
99+
let unpacked: i64 = Castable64::decode(packed);
100+
assert(val == unpacked, 'i64 min Castable64 mismatch');
101+
}

packages/utils/src/storage/utils.cairo

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use starknet::Store;
21
use starknet::storage::{
32
Mutable, StorageAsPointer, StoragePointer, StoragePointerReadAccess, StoragePointerWriteAccess,
43
};
4+
use starknet::{EthAddress, Store};
5+
56

67
pub trait AddToStorage<T> {
78
type Value;
@@ -60,3 +61,154 @@ pub impl StoragePathSubFromStorageImpl<
6061
new_value
6162
}
6263
}
64+
65+
66+
/// Trait for types that can be stored as 160 bits.
67+
///
68+
/// The format is a tuple `(low: u128, high: u32)`.
69+
///
70+
/// # Requirements
71+
/// - `encode` should be injective: distinct values map to distinct tuples.
72+
/// - `decode` should be the inverse of `encode` on its image:
73+
/// decode(encode(v)) == v
74+
pub trait Castable160<V> {
75+
/// Convert a value into its 160-bit representation `(low, high)`.
76+
fn encode(value: V) -> (u128, u32);
77+
78+
/// Convert a 160-bit representation back into the original value.
79+
fn decode(value: (u128, u32)) -> V;
80+
}
81+
82+
/// `Castable160` implementation for primitive integer types ('u8', 'u16', 'u32', 'u64' and 'u128'.)
83+
/// that fit entirely in 128 bits.
84+
pub impl PrimitiveCastable160<T, +Into<T, u128>, +TryInto<u128, T>, +Drop<T>> of Castable160<T> {
85+
fn encode(value: T) -> (u128, u32) {
86+
(value.into(), 0)
87+
}
88+
fn decode(value: (u128, u32)) -> T {
89+
let (low, high) = value;
90+
assert(high == 0, 'Castable160: high bits not 0');
91+
low.try_into().unwrap()
92+
}
93+
}
94+
95+
/// Trait to define the offset for signed integer.
96+
trait SignedIntegerOffset<T> {
97+
fn offset() -> felt252;
98+
}
99+
100+
impl I8Offset of SignedIntegerOffset<i8> {
101+
fn offset() -> felt252 {
102+
// 2 ** 7
103+
128
104+
}
105+
}
106+
107+
impl I16Offset of SignedIntegerOffset<i16> {
108+
fn offset() -> felt252 {
109+
// 2** 15
110+
32768
111+
}
112+
}
113+
114+
impl I32Offset of SignedIntegerOffset<i32> {
115+
fn offset() -> felt252 {
116+
// 2** 31
117+
2147483648
118+
}
119+
}
120+
121+
impl I64Offset of SignedIntegerOffset<i64> {
122+
fn offset() -> felt252 {
123+
// 2** 63
124+
9223372036854775808
125+
}
126+
}
127+
128+
impl I128Offset of SignedIntegerOffset<i128> {
129+
fn offset() -> felt252 {
130+
// 2** 127
131+
170141183460469231731687303715884105728
132+
}
133+
}
134+
135+
pub impl SignedIntegerCastable160<
136+
T, +SignedIntegerOffset<T>, +Into<T, felt252>, +TryInto<felt252, T>, +Drop<T>,
137+
> of Castable160<T> {
138+
fn encode(value: T) -> (u128, u32) {
139+
let val_felt: felt252 = value.into();
140+
let offset = SignedIntegerOffset::<T>::offset();
141+
let val_u128: u128 = (val_felt + offset).try_into().unwrap();
142+
(val_u128, 0)
143+
}
144+
fn decode(value: (u128, u32)) -> T {
145+
let (low, high) = value;
146+
assert(high == 0, 'Castable160: high bits not 0');
147+
let val_felt: felt252 = low.into();
148+
let offset = SignedIntegerOffset::<T>::offset();
149+
(val_felt - offset).try_into().unwrap()
150+
}
151+
}
152+
153+
pub impl EthAddressCastable160 of Castable160<EthAddress> {
154+
fn encode(value: EthAddress) -> (u128, u32) {
155+
let felt_val: felt252 = value.into();
156+
let u256_val: u256 = felt_val.into();
157+
let low = u256_val.low;
158+
let high: u32 = u256_val.high.try_into().unwrap();
159+
(low, high)
160+
}
161+
fn decode(value: (u128, u32)) -> EthAddress {
162+
let (low, high) = value;
163+
let u256_val = u256 { low, high: high.into() };
164+
u256_val.try_into().unwrap()
165+
}
166+
}
167+
168+
/// Trait for types that can be stored as 64 bits.
169+
///
170+
/// The format is a `u64`.
171+
///
172+
/// # Requirements
173+
/// - `encode` should be injective: distinct values map to distinct tuples.
174+
/// - `decode` should be the inverse of `encode` on its image:
175+
/// decode(encode(v)) == v
176+
pub trait Castable64<V> {
177+
/// Convert a value into its 64-bit representation.
178+
fn encode(value: V) -> u64;
179+
/// Convert a 64-bit representation back into the original value.
180+
fn decode(value: u64) -> V;
181+
}
182+
183+
pub impl PrimitiveCastable64<T, +Into<T, u64>, +TryInto<u64, T>, +Drop<T>> of Castable64<T> {
184+
fn encode(value: T) -> u64 {
185+
value.into()
186+
}
187+
fn decode(value: u64) -> T {
188+
value.try_into().unwrap()
189+
}
190+
}
191+
192+
193+
/// Trait to check if a type fits in 64 bits.
194+
trait FitsIn64<T> {}
195+
impl I8FitsIn64 of FitsIn64<i8> {}
196+
impl I16FitsIn64 of FitsIn64<i16> {}
197+
impl I32FitsIn64 of FitsIn64<i32> {}
198+
impl I64FitsIn64 of FitsIn64<i64> {}
199+
200+
pub impl SignedIntegerCastable64<
201+
T, +FitsIn64<T>, +SignedIntegerOffset<T>, +Into<T, felt252>, +TryInto<felt252, T>, +Drop<T>,
202+
> of Castable64<T> {
203+
fn encode(value: T) -> u64 {
204+
let val_felt: felt252 = value.into();
205+
let offset = SignedIntegerOffset::<T>::offset();
206+
let val_u64: u64 = (val_felt + offset).try_into().unwrap();
207+
val_u64
208+
}
209+
fn decode(value: u64) -> T {
210+
let val_felt: felt252 = value.into();
211+
let offset = SignedIntegerOffset::<T>::offset();
212+
(val_felt - offset).try_into().unwrap()
213+
}
214+
}

0 commit comments

Comments
 (0)