Skip to content

Commit 8755b68

Browse files
authored
feat: ByteArray type (#682)
1 parent 093d5ec commit 8755b68

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

starknet-core/src/types/byte_array.rs

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
use alloc::{string::*, vec::*};
2+
3+
use num_traits::ToPrimitive;
4+
5+
use crate::{
6+
codec::{Decode, Encode, Error as CodecError, FeltWriter},
7+
types::Felt,
8+
};
9+
10+
const BYTES_PER_SLOT: usize = 31;
11+
const FELT_BYTE_SIZE: usize = 32;
12+
13+
/// The `ByteArray` type in Cairo.
14+
#[derive(Debug, Clone, PartialEq, Eq)]
15+
pub struct ByteArray(Vec<u8>);
16+
17+
impl ByteArray {
18+
/// Returns the number of bytes in the array, also referred to as its 'length'.
19+
pub fn len(&self) -> usize {
20+
self.0.len()
21+
}
22+
23+
/// Returns `true` if the array contains no bytes.
24+
#[must_use]
25+
pub fn is_empty(&self) -> bool {
26+
self.0.is_empty()
27+
}
28+
}
29+
30+
impl From<Vec<u8>> for ByteArray {
31+
fn from(value: Vec<u8>) -> Self {
32+
Self(value)
33+
}
34+
}
35+
36+
impl From<&str> for ByteArray {
37+
fn from(value: &str) -> Self {
38+
Self(value.as_bytes().to_vec())
39+
}
40+
}
41+
42+
impl From<ByteArray> for Vec<u8> {
43+
fn from(value: ByteArray) -> Self {
44+
value.0
45+
}
46+
}
47+
48+
impl TryFrom<ByteArray> for String {
49+
type Error = FromUtf8Error;
50+
51+
fn try_from(value: ByteArray) -> Result<Self, Self::Error> {
52+
Self::from_utf8(value.0)
53+
}
54+
}
55+
56+
impl Encode for ByteArray {
57+
fn encode<W: FeltWriter>(&self, writer: &mut W) -> Result<(), CodecError> {
58+
writer.write((self.len() / BYTES_PER_SLOT).into());
59+
let mut chunks_iter = self.0.chunks_exact(BYTES_PER_SLOT);
60+
for full_slot in chunks_iter.by_ref() {
61+
writer.write(Felt::from_bytes_be_slice(full_slot));
62+
}
63+
64+
let last_chunk = chunks_iter.remainder();
65+
writer.write(Felt::from_bytes_be_slice(last_chunk));
66+
writer.write(last_chunk.len().into());
67+
68+
Ok(())
69+
}
70+
}
71+
72+
impl<'a> Decode<'a> for ByteArray {
73+
fn decode_iter<T>(iter: &mut T) -> Result<Self, CodecError>
74+
where
75+
T: Iterator<Item = &'a Felt>,
76+
{
77+
let length = iter.next().ok_or_else(CodecError::input_exhausted)?;
78+
let length = length
79+
.to_usize()
80+
.ok_or_else(|| CodecError::value_out_of_range(length, "usize"))?;
81+
82+
let mut result = Vec::<u8>::with_capacity(length * BYTES_PER_SLOT + BYTES_PER_SLOT - 1);
83+
84+
for _ in 0..length {
85+
let full_slot = iter.next().ok_or_else(CodecError::input_exhausted)?;
86+
result.extend_from_slice(&full_slot.to_bytes_be()[1..]);
87+
}
88+
89+
let pending_word = iter.next().ok_or_else(CodecError::input_exhausted)?;
90+
let pending_word_len = iter.next().ok_or_else(CodecError::input_exhausted)?;
91+
let pending_word_len = pending_word_len
92+
.to_usize()
93+
.ok_or_else(|| CodecError::value_out_of_range(pending_word_len, "usize"))?;
94+
result
95+
.extend_from_slice(&pending_word.to_bytes_be()[(FELT_BYTE_SIZE - pending_word_len)..]);
96+
97+
Ok(Self(result))
98+
}
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
use super::*;
104+
105+
#[test]
106+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
107+
fn test_string_encode_decode_roundtrip() {
108+
let test_data = [
109+
(
110+
"",
111+
vec![
112+
Felt::from_hex_unchecked(
113+
"0x0000000000000000000000000000000000000000000000000000000000000000",
114+
),
115+
Felt::from_hex_unchecked(
116+
"0x0000000000000000000000000000000000000000000000000000000000000000",
117+
),
118+
Felt::from_hex_unchecked(
119+
"0x0000000000000000000000000000000000000000000000000000000000000000",
120+
),
121+
],
122+
),
123+
(
124+
"abc",
125+
vec![
126+
Felt::from_hex_unchecked(
127+
"0x0000000000000000000000000000000000000000000000000000000000000000",
128+
),
129+
Felt::from_hex_unchecked(
130+
"0x0000000000000000000000000000000000000000000000000000000000616263",
131+
),
132+
Felt::from_hex_unchecked(
133+
"0x0000000000000000000000000000000000000000000000000000000000000003",
134+
),
135+
],
136+
),
137+
(
138+
"000000000011111111112222222222",
139+
vec![
140+
Felt::from_hex_unchecked(
141+
"0x0000000000000000000000000000000000000000000000000000000000000000",
142+
),
143+
Felt::from_hex_unchecked(
144+
"0x0000303030303030303030303131313131313131313132323232323232323232",
145+
),
146+
Felt::from_hex_unchecked(
147+
"0x000000000000000000000000000000000000000000000000000000000000001e",
148+
),
149+
],
150+
),
151+
(
152+
"0000000000111111111122222222223",
153+
vec![
154+
Felt::from_hex_unchecked(
155+
"0x0000000000000000000000000000000000000000000000000000000000000001",
156+
),
157+
Felt::from_hex_unchecked(
158+
"0x0030303030303030303030313131313131313131313232323232323232323233",
159+
),
160+
Felt::from_hex_unchecked(
161+
"0x0000000000000000000000000000000000000000000000000000000000000000",
162+
),
163+
Felt::from_hex_unchecked(
164+
"0x0000000000000000000000000000000000000000000000000000000000000000",
165+
),
166+
],
167+
),
168+
(
169+
"00000000001111111111222222222233",
170+
vec![
171+
Felt::from_hex_unchecked(
172+
"0x0000000000000000000000000000000000000000000000000000000000000001",
173+
),
174+
Felt::from_hex_unchecked(
175+
"0x0030303030303030303030313131313131313131313232323232323232323233",
176+
),
177+
Felt::from_hex_unchecked(
178+
"0x0000000000000000000000000000000000000000000000000000000000000033",
179+
),
180+
Felt::from_hex_unchecked(
181+
"0x0000000000000000000000000000000000000000000000000000000000000001",
182+
),
183+
],
184+
),
185+
(
186+
"00000000001111111111222222222233333333334444444444\
187+
55555555556666666666777777777788888888889999999999",
188+
vec![
189+
Felt::from_hex_unchecked(
190+
"0x0000000000000000000000000000000000000000000000000000000000000003",
191+
),
192+
Felt::from_hex_unchecked(
193+
"0x0030303030303030303030313131313131313131313232323232323232323233",
194+
),
195+
Felt::from_hex_unchecked(
196+
"0x0033333333333333333334343434343434343434353535353535353535353636",
197+
),
198+
Felt::from_hex_unchecked(
199+
"0x0036363636363636363737373737373737373738383838383838383838393939",
200+
),
201+
Felt::from_hex_unchecked(
202+
"0x0000000000000000000000000000000000000000000000000039393939393939",
203+
),
204+
Felt::from_hex_unchecked(
205+
"0x0000000000000000000000000000000000000000000000000000000000000007",
206+
),
207+
],
208+
),
209+
];
210+
211+
for (string, expected) in test_data {
212+
let bytes: ByteArray = string.into();
213+
214+
let mut encoded = vec![];
215+
bytes.encode(&mut encoded).unwrap();
216+
assert_eq!(encoded, expected);
217+
218+
let decoded = ByteArray::decode(&expected).unwrap();
219+
assert_eq!(String::try_from(decoded).unwrap(), string);
220+
}
221+
}
222+
}

starknet-core/src/types/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ pub use msg::MsgToL2;
6565
mod call;
6666
pub use call::Call;
6767

68+
mod byte_array;
69+
pub use byte_array::ByteArray;
70+
6871
// TODO: move generated request code to `starknet-providers`
6972
/// Module containing JSON-RPC request types.
7073
pub mod requests;

0 commit comments

Comments
 (0)