diff --git a/src/data_structures/src/bit_array.cairo b/src/data_structures/src/bit_array.cairo new file mode 100644 index 00000000..518a22ab --- /dev/null +++ b/src/data_structures/src/bit_array.cairo @@ -0,0 +1,518 @@ +use array::{serialize_array_helper, deserialize_array_helper}; +use bytes_31::{ + one_shift_left_bytes_u128, one_shift_left_bytes_felt252, bytes31, BYTES_IN_U128, + BYTES_IN_BYTES31, +}; +use byte_array::BYTES_IN_BYTES31_MINUS_ONE; +use integer::u512; +use serde::into_felt252_based::SerdeImpl; +use traits::DivRem; + +const SELECT_BIT: u128 = 0b10; + +#[derive(Clone, Drop)] +struct BitArray { + data: Array, + current: felt252, + read_pos: usize, + write_pos: usize, +} + +impl BitArrayDefaultImpl of Default { + fn default() -> BitArray { + BitArray { data: array![], current: 0, read_pos: 0, write_pos: 0, } + } +} + +trait BitArrayTrait { + /// Appends a single bit to the BitArray + /// # Arguments + /// `bit` - either true or false, representing a single bit to be appended + fn append_bit(ref self: BitArray, bit: bool); + /// Reads a single bit from the array + /// # Arguments + /// `index` - the index into the array to read + /// # Returns + /// `Option` - if the index is found, the stored bool is returned + fn at(self: @BitArray, index: usize) -> Option; + /// The current length of the BitArray + /// # Returns + /// `usize` - length in bits of the BitArray + fn len(self: @BitArray) -> usize; + /// Returns and removes the first element of the BitArray + /// # Returns + /// `Option` - If the array is non-empty, a `bool` is removed from the front and returned + fn pop_front(ref self: BitArray) -> Option; + /// Reads a single word of the specified length up to 248 bits in big endian bit representation + /// # Arguments + /// `length` - The bit length of the word to read, max 248 + /// # Returns + /// `Option` - If there are `length` bits remaining, the word is returned as felt252 + fn read_word_be(ref self: BitArray, length: usize) -> Option; + /// Reads a single word of the specified length upto 256 bits in big endian representation. + /// For words shorter than (or equal to) 248 bits use `read_word_be(...)` instead. + /// # Arguments + /// `length` - The bit length of the word to read, max 256 + /// # Returns + /// `Option` - If there are `length` bits remaining, the word is returned as u256 + fn read_word_be_u256(ref self: BitArray, length: usize) -> Option; + /// Reads a single word of the specified length upto 512 bits in big endian representation. + /// For words shorter than (or equal to) 256 bits consider the other read calls instead. + /// # Arguments + /// `length` - The bit length of the word to read, max 512 + /// # Returns + /// `Option` - If there are `length` bits remaining, the word is returned as u512 + fn read_word_be_u512(ref self: BitArray, length: usize) -> Option; + /// Writes the bits of the specified length from `word` onto the BitArray + /// in big endian representation + /// # Arguemnts + /// `word` - The value to store onto the bit array of type `felt252` + /// `length` - The length of the word in bits, maximum 248 + fn write_word_be(ref self: BitArray, word: felt252, length: usize); + /// Writes the bits of the specified length from `word` onto the BitArray + /// in big endian representation + /// # Arguemnts + /// `word` - The value to store onto the bit array of type `u256` + /// `length` - The length of the word in bits, maximum 256 + fn write_word_be_u256(ref self: BitArray, word: u256, length: usize); + /// Writes the bits of the specified length from `word` onto the BitArray + /// in big endian representation + /// # Arguemnts + /// `word` - The value to store onto the bit array of type `u512` + /// `length` - The length of the word in bits, maximum 512 + fn write_word_be_u512(ref self: BitArray, word: u512, length: usize); + /// Reads a single word of the specified length up to 248 bits in little endian bit representation + /// # Arguments + /// `length` - The bit length of the word to read, max 248 + /// # Returns + /// `Option` - If there are `length` bits remaining, the word is returned as felt252 + fn read_word_le(ref self: BitArray, length: usize) -> Option; + /// Reads a single word of the specified length upto 256 bits in little endian representation. + /// For words shorter than (or equal to) 248 bits use `read_word_be(...)` instead. + /// # Arguments + /// `length` - The bit length of the word to read, max 256 + /// # Returns + /// `Option` - If there are `length` bits remaining, the word is returned as u256 + fn read_word_le_u256(ref self: BitArray, length: usize) -> Option; + /// Reads a single word of the specified length upto 512 bits in little endian representation. + /// For words shorter than (or equal to) 256 bits consider the other read calls instead. + /// # Arguments + /// `length` - The bit length of the word to read, max 512 + /// # Returns + /// `Option` - If there are `length` bits remaining, the word is returned as u512 + fn read_word_le_u512(ref self: BitArray, length: usize) -> Option; + /// Writes the bits of the specified length from `word` onto the BitArray + /// in little endian representation + /// # Arguemnts + /// `word` - The value to store onto the bit array of type `felt252` + /// `length` - The length of the word in bits, maximum 248 + fn write_word_le(ref self: BitArray, word: felt252, length: usize); + /// Writes the bits of the specified length from `word` onto the BitArray + /// in little endian representation + /// # Arguemnts + /// `word` - The value to store onto the bit array of type `u256` + /// `length` - The length of the word in bits, maximum 256 + fn write_word_le_u256(ref self: BitArray, word: u256, length: usize); + /// Writes the bits of the specified length from `word` onto the BitArray + /// in little endian representation + /// # Arguemnts + /// `word` - The value to store onto the bit array of type `u512` + /// `length` - The length of the word in bits, maximum 512 + fn write_word_le_u512(ref self: BitArray, word: u512, length: usize); +} + +impl BitArrayImpl of BitArrayTrait { + fn append_bit(ref self: BitArray, bit: bool) { + let (byte_number, bit_offset) = DivRem::div_rem( + self.write_pos, 8_usize.try_into().unwrap() + ); + let byte_offset = BYTES_IN_BYTES31_MINUS_ONE - (byte_number % BYTES_IN_BYTES31); + let bit_offset = 7 - bit_offset; + self.write_pos += 1; + if bit { + self.current += one_shift_left_bytes_felt252(byte_offset).into() + * shift_bit(bit_offset).into(); + } + if byte_offset + bit_offset == 0 { + self.data.append(self.current.try_into().unwrap()); + self.current = 0; + } + } + + fn at(self: @BitArray, index: usize) -> Option { + if index >= self.len() { + Option::None + } else { + let (word, byte_offset, bit_offset) = self.word_and_offset(index + *self.read_pos); + Option::Some(select(word, byte_offset, bit_offset)) + } + } + + #[inline] + fn len(self: @BitArray) -> usize { + *self.write_pos - *self.read_pos + } + + fn pop_front(ref self: BitArray) -> Option { + let result = self.at(0)?; + self.read_pos += 1; + Option::Some(result) + } + + fn read_word_be(ref self: BitArray, length: usize) -> Option { + assert(length <= 248, 'illegal length'); + self._read_word_be_recursive(0, length) + } + + fn read_word_be_u256(ref self: BitArray, length: usize) -> Option { + assert(length <= 256, 'illegal length'); + Option::Some( + if length > 128 { + let high = self._read_word_be_recursive(0, length - 128)?.try_into().unwrap(); + let low = self._read_word_be_recursive(0, 128)?.try_into().unwrap(); + u256 { low, high } + } else { + let high = 0; + let low = self._read_word_be_recursive(0, length)?.try_into().unwrap(); + u256 { low, high } + } + ) + } + + fn read_word_be_u512(ref self: BitArray, length: usize) -> Option { + assert(length <= 512, 'illegal length'); + Option::Some( + if length > 384 { + let limb3 = self._read_word_be_recursive(0, length - 384)?.try_into().unwrap(); + let limb2 = self._read_word_be_recursive(0, 128)?.try_into().unwrap(); + let limb1 = self._read_word_be_recursive(0, 128)?.try_into().unwrap(); + let limb0 = self._read_word_be_recursive(0, 128)?.try_into().unwrap(); + u512 { limb0, limb1, limb2, limb3 } + } else if length > 256 { + let limb3 = 0; + let limb2 = self._read_word_be_recursive(0, length - 256)?.try_into().unwrap(); + let limb1 = self._read_word_be_recursive(0, 128)?.try_into().unwrap(); + let limb0 = self._read_word_be_recursive(0, 128)?.try_into().unwrap(); + u512 { limb0, limb1, limb2, limb3 } + } else if length > 128 { + let limb3 = 0; + let limb2 = 0; + let limb1 = self._read_word_be_recursive(0, length - 256)?.try_into().unwrap(); + let limb0 = self._read_word_be_recursive(0, 128)?.try_into().unwrap(); + u512 { limb0, limb1, limb2, limb3 } + } else { + let limb3 = 0; + let limb2 = 0; + let limb1 = 0; + let limb0 = self._read_word_be_recursive(0, length)?.try_into().unwrap(); + u512 { limb0, limb1, limb2, limb3 } + } + ) + } + + fn write_word_be(ref self: BitArray, word: felt252, length: usize) { + assert(length <= 248, 'illegal length'); + if length == 0 { + return; + } + let (mut byte_offset, mut bit_offset) = DivRem::div_rem( + length - 1, 8_usize.try_into().unwrap() + ); + loop { + self.append_bit(select(word, byte_offset, bit_offset)); + if bit_offset == 0 { + if byte_offset == 0 { + break; + } else { + bit_offset = 8; + byte_offset -= 1; + } + } + bit_offset -= 1; + }; + } + + fn write_word_be_u256(ref self: BitArray, word: u256, length: usize) { + assert(length <= 256, 'illegal length'); + let u256{low, high } = word; + if length > 128 { + self.write_word_be(high.into(), length - 128); + self.write_word_be(low.into(), 128); + } else { + self.write_word_be(low.into(), length); + } + } + + fn write_word_be_u512(ref self: BitArray, word: u512, length: usize) { + assert(length <= 512, 'illegal length'); + let u512{limb0, limb1, limb2, limb3 } = word; + + if length > 384 { + self.write_word_be(limb3.into(), length - 384); + self.write_word_be(limb2.into(), 128); + self.write_word_be(limb1.into(), 128); + self.write_word_be(limb0.into(), 128); + } else if length > 256 { + self.write_word_be(limb2.into(), length - 256); + self.write_word_be(limb1.into(), 128); + self.write_word_be(limb0.into(), 128); + } else if length > 128 { + self.write_word_be(limb1.into(), length - 128); + self.write_word_be(limb0.into(), 128); + } else { + self.write_word_be(limb0.into(), length); + } + } + + fn read_word_le(ref self: BitArray, length: usize) -> Option { + assert(length <= 248, 'illegal length'); + if length == 0 { + return Option::None; + } + let (byte_limit, bit_limit) = DivRem::div_rem(length, 8_usize.try_into().unwrap()); + let mut bit_offset = 0_usize; + let mut byte_offset = 0_usize; + let mut result: Option = Option::Some(0); + loop { + if bit_offset == bit_limit && byte_offset == byte_limit { + break; + } + match self.pop_front() { + Option::Some(bit) => { + if bit { + result = + Option::Some( + (one_shift_left_bytes_felt252(byte_offset) + * shift_bit(bit_offset).into()) + + result.unwrap() + ); + } + }, + Option::None => { + result = Option::None; + break; + } + } + if bit_offset == 7 { + byte_offset += 1; + bit_offset = 0; + } else { + bit_offset += 1; + } + }; + result + } + + fn read_word_le_u256(ref self: BitArray, length: usize) -> Option { + assert(length <= 256, 'illegal length'); + Option::Some( + if length > 128 { + let low = self.read_word_le(128)?.try_into().unwrap(); + let high = self.read_word_le(length - 128)?.try_into().unwrap(); + u256 { low, high } + } else { + let low = self.read_word_le(length)?.try_into().unwrap(); + let high = 0; + u256 { low, high } + } + ) + } + + fn read_word_le_u512(ref self: BitArray, length: usize) -> Option { + assert(length <= 512, 'illegal length'); + Option::Some( + if length > 384 { + let limb0 = self.read_word_le(128)?; + let limb1 = self.read_word_le(128)?; + let limb2 = self.read_word_le(128)?; + let limb3 = self.read_word_le(length - 384)?; + u512 { + limb0: limb0.try_into().unwrap(), + limb1: limb1.try_into().unwrap(), + limb2: limb2.try_into().unwrap(), + limb3: limb3.try_into().unwrap() + } + } else if length > 256 { + let limb0 = self.read_word_le(128)?; + let limb1 = self.read_word_le(128)?; + let limb2 = self.read_word_le(length - 256)?; + let limb3 = 0; + u512 { + limb0: limb0.try_into().unwrap(), + limb1: limb1.try_into().unwrap(), + limb2: limb2.try_into().unwrap(), + limb3: limb3 + } + } else if length > 128 { + let limb0 = self.read_word_le(128)?; + let limb1 = self.read_word_le(length - 128)?; + let limb2 = 0; + let limb3 = 0; + u512 { + limb0: limb0.try_into().unwrap(), + limb1: limb1.try_into().unwrap(), + limb2: limb2, + limb3: limb3 + } + } else { + let limb0 = self.read_word_le(length)?; + let limb1 = 0; + let limb2 = 0; + let limb3 = 0; + u512 { limb0: limb0.try_into().unwrap(), limb1: limb1, limb2: limb2, limb3: limb3 } + } + ) + } + + fn write_word_le(ref self: BitArray, word: felt252, length: usize) { + assert(length <= 248, 'illegal length'); + let u256{low, high } = word.into(); + if length > 128 { + self._write_word_le_recursive(low, 128); + self._write_word_le_recursive(high, length - 128); + } else { + self._write_word_le_recursive(low, length); + } + } + + fn write_word_le_u256(ref self: BitArray, word: u256, length: usize) { + assert(length <= 256, 'illegal length'); + let u256{low, high } = word; + if length > 128 { + self.write_word_le(low.into(), 128); + self.write_word_le(high.into(), length - 128); + } else { + self.write_word_le(low.into(), length); + } + } + + fn write_word_le_u512(ref self: BitArray, word: u512, length: usize) { + assert(length <= 512, 'illegal length'); + let u512{limb0, limb1, limb2, limb3 } = word; + if length > 384 { + self.write_word_le(limb0.into(), 128); + self.write_word_le(limb1.into(), 128); + self.write_word_le(limb2.into(), 128); + self.write_word_le(limb3.into(), length - 384); + } else if length > 256 { + self.write_word_le(limb0.into(), 128); + self.write_word_le(limb1.into(), 128); + self.write_word_le(limb2.into(), length - 256); + } else if length > 128 { + self.write_word_le(limb0.into(), 128); + self.write_word_le(limb1.into(), length - 128); + } else { + self.write_word_le(limb0.into(), length); + } + } +} + +#[generate_trait] +impl BitArrayInternalImpl of BitArrayInternalTrait { + #[inline] + fn word_and_offset(self: @BitArray, index: usize) -> (felt252, usize, usize) { + let (byte_number, bit_offset) = DivRem::div_rem(index, 8_usize.try_into().unwrap()); + let (word_index, byte_offset) = DivRem::div_rem( + byte_number, BYTES_IN_BYTES31.try_into().unwrap() + ); + let bit_offset = 7_usize - bit_offset; + let byte_offset = BYTES_IN_BYTES31_MINUS_ONE - byte_offset; + let word = if word_index == self.data.len() { + *self.current + } else { + let w = *self.data.at(word_index); + w.into() + }; + (word, byte_offset, bit_offset) + } + + fn _write_word_le_recursive(ref self: BitArray, word: u128, length: usize) { + assert(length <= 128, 'illegal length'); + if length == 0 { + return; + } + let bit = word % SELECT_BIT; + self.append_bit(bit == 1); + self._write_word_le_recursive(word / SELECT_BIT, length - 1); + } + + fn _read_word_be_recursive( + ref self: BitArray, current: felt252, length: usize + ) -> Option { + if length == 0 { + return Option::Some(current); + } + + let bit = self.pop_front()?; + let next = if bit { + 1 + } else { + 0 + }; + self._read_word_be_recursive(current * 2 + next, length - 1) + } +} + +impl BitArrayIndexView of IndexView { + fn index(self: @BitArray, index: usize) -> bool { + self.at(index).expect('Index out of bounds') + } +} + +impl BitArraySerde of Serde { + fn serialize(self: @BitArray, ref output: Array) { + let length = self.len(); + length.serialize(ref output); + let bytes31_arr = self.data.span(); + serialize_array_helper(bytes31_arr, ref output); + output.append(*self.current); + } + + fn deserialize(ref serialized: Span) -> Option { + let length = *serialized.pop_front()?; + let length: usize = length.try_into().unwrap(); + let length_in_felts = length / 8 / BYTES_IN_BYTES31; + let bytes31_arr = deserialize_array_helper( + ref serialized, array![], length_in_felts.into() + )?; + let current = *serialized.pop_front()?; + Option::Some( + BitArray { data: bytes31_arr, current: current, read_pos: 0, write_pos: length, } + ) + } +} + +// helper +#[inline(always)] +fn shift_bit(number: usize) -> u8 { + if number == 0 { + 1_u8 + } else if number == 1 { + 0b10_u8 + } else if number == 2 { + 0b100_u8 + } else if number == 3 { + 0b1000_u8 + } else if number == 4 { + 0b10000_u8 + } else if number == 5 { + 0b100000_u8 + } else if number == 6 { + 0b1000000_u8 + } else if number == 7 { + 0b10000000_u8 + } else { + panic_with_felt252('invalid shift') + } +} + +#[inline(always)] +fn select(word: felt252, byte_index: usize, bit_index: usize) -> bool { + let u256{low, high } = word.into(); + let shifted_bytes = if byte_index >= BYTES_IN_U128 { + high / one_shift_left_bytes_u128(byte_index - BYTES_IN_U128) + } else { + low / one_shift_left_bytes_u128(byte_index) + }; + (shifted_bytes / shift_bit(bit_index).into()) % SELECT_BIT == 1 +} diff --git a/src/data_structures/src/lib.cairo b/src/data_structures/src/lib.cairo index f4bb99fb..f7b8d55c 100644 --- a/src/data_structures/src/lib.cairo +++ b/src/data_structures/src/lib.cairo @@ -1,4 +1,5 @@ mod array_ext; +mod bit_array; mod byte_array_ext; mod byte_array_reader; mod queue; diff --git a/src/data_structures/src/tests.cairo b/src/data_structures/src/tests.cairo index 555a33cb..6cf4c864 100644 --- a/src/data_structures/src/tests.cairo +++ b/src/data_structures/src/tests.cairo @@ -1,4 +1,5 @@ mod array_ext_test; +mod bit_array_test; mod byte_array_ext_test; mod byte_array_reader_test; mod queue_test; diff --git a/src/data_structures/src/tests/bit_array_test.cairo b/src/data_structures/src/tests/bit_array_test.cairo new file mode 100644 index 00000000..34f25e3e --- /dev/null +++ b/src/data_structures/src/tests/bit_array_test.cairo @@ -0,0 +1,343 @@ +use alexandria_data_structures::bit_array::{BitArray, BitArrayTrait, shift_bit}; +use integer::u512; +use integer::BoundedInt; +use bytes_31::one_shift_left_bytes_felt252; + +#[test] +#[available_gas(30000000)] +fn test_append_bit() { + let mut ba: BitArray = Default::default(); + let mut c = 250; + loop { + if c == 0 { + break; + } + ba.append_bit(true); + ba.append_bit(false); + c -= 1; + }; + let val: bytes31 = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + .try_into() + .unwrap(); + let expected: Array = array![val, val,]; + assert(ba.data == expected, 'illegal array'); + assert( + ba.current == 0xa * one_shift_left_bytes_felt252(30) * shift_bit(4).into(), + 'expected current 0xa' + ); +} + +#[test] +#[available_gas(20000000)] +fn test_at() { + let ba = sample_bit_array(); + let mut index: usize = 0; + loop { + if index == 8 * 16 - 1 { + // last value + assert(ba[index] == false, 'expected false'); + break; + } + assert(ba.at(index).unwrap() == true, 'expected true'); + index += 1; + }; +} + +#[test] +#[available_gas(2000000)] +fn test_at_none() { + let ba = sample_bit_array(); + assert(ba.at(8 * 16).is_none(), 'expected none'); +} + +#[test] +#[available_gas(20000000)] +fn test_index() { + let ba = sample_bit_array(); + assert(ba[0] == true, 'expected true'); + assert(ba[8 * 16 - 1] == false, 'expected false'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('Index out of bounds',))] +fn test_index_fail() { + let ba = sample_bit_array(); + ba[8 * 16]; +} + +#[test] +#[available_gas(2000000)] +fn test_len() { + let mut ba = sample_bit_array(); + let expected = 8 * 16; + assert(ba.len() == expected, 'expected len 8 * 16'); + ba.append_bit(true); + assert(ba.len() == expected + 1, 'expected len 8 * 16 + 1'); + let _ = ba.pop_front(); + assert(ba.len() == expected, 'expected len 8 * 16'); +} + +#[test] +#[available_gas(2000000)] +fn test_pop_front() { + let mut ba = sample_bit_array(); + assert(ba.pop_front() == Option::Some(true), 'expected (some) true'); +} + +#[test] +#[available_gas(2000000)] +fn test_pop_front_empty() { + let mut ba: BitArray = Default::default(); + assert(ba.pop_front() == Option::None, 'expected none'); +} + +#[test] +#[available_gas(20000000)] +fn test_read_word_be() { + let mut ba = sample_bit_array(); + assert( + ba.read_word_be(length: 128).unwrap() == BoundedInt::::max().into() - 1, + 'expected max - 1' + ); +} + +#[test] +#[available_gas(20000000)] +fn test_read_word_le() { + let mut ba = sample_bit_array(); + assert( + ba.read_word_le(length: 128).unwrap() == 0x7fffffffffffffffffffffffffffffff, + 'unexpected value' + ); +} + +#[test] +#[available_gas(40000000)] +fn test_read_word_be_u256() { + let mut ba = sample_bit_array(); + let low = 0x101112131415161718191a1b1c1d1e1f_u128; + ba.write_word_be(low.into(), 128); + let high = 0xfffffffffffffffffffffffffffffffe_u128; + assert(ba.read_word_be_u256(length: 256).unwrap() == u256 { low, high }, 'unexpected value'); +} + +#[test] +#[available_gas(40000000)] +fn test_read_word_le_u256() { + let mut ba = sample_bit_array(); + let low = 0x7fffffffffffffffffffffffffffffff_u128; + let high = 0x101112131415161718191a1b1c1d1e1f_u128; + ba.write_word_le(high.into(), 128); + assert(ba.read_word_le_u256(length: 256).unwrap() == u256 { low, high }, 'unexpected value'); +} + +#[test] +#[available_gas(70000000)] +fn test_read_word_be_u512() { + let mut ba = sample_bit_array(); + let limb0 = 0x101112131415161718191a1b1c1d1e1f_u128; + let limb1 = 0x202122232425262728292a2b2c2d2e2f_u128; + let limb2 = 0x303132333435363738393a3b3c3d3e3f_u128; + ba.write_word_be(limb2.into(), 128); + ba.write_word_be(limb1.into(), 128); + ba.write_word_be(limb0.into(), 128); + let limb3 = 0xfffffffffffffffffffffffffffffffe_u128; + assert( + ba.read_word_be_u512(length: 512).unwrap() == u512 { limb0, limb1, limb2, limb3 }, + 'unexpected value' + ); +} + +#[test] +#[available_gas(70000000)] +fn test_read_word_le_u512() { + let mut ba = sample_bit_array(); + let limb1 = 0x101112131415161718191a1b1c1d1e1f_u128; + let limb2 = 0x202122232425262728292a2b2c2d2e2f_u128; + let limb3 = 0x303132333435363738393a3b3c3d3e3f_u128; + ba.write_word_le(limb1.into(), 128); + ba.write_word_le(limb2.into(), 128); + ba.write_word_le(limb3.into(), 128); + let limb0 = 0x7fffffffffffffffffffffffffffffff_u128; + assert( + ba.read_word_le_u512(length: 512).unwrap() == u512 { limb0, limb1, limb2, limb3 }, + 'unexpected value' + ); +} + +#[test] +#[available_gas(20000000)] +fn test_read_word_be_half() { + let mut ba = sample_bit_array(); + assert(ba.read_word_be(64).unwrap() == 0xffffffffffffffff, 'unexpected result'); + assert(ba.read_word_be(64).unwrap() == 0xfffffffffffffffe, 'unexpected result'); +} + +#[test] +#[available_gas(20000000)] +fn test_read_word_le_half() { + let mut ba = sample_bit_array(); + assert(ba.read_word_le(64).unwrap() == 0xffffffffffffffff, 'unexpected result'); + assert(ba.read_word_le(64).unwrap() == 0x7fffffffffffffff, 'unexpected result'); +} + +#[test] +#[available_gas(20000000)] +fn test_write_word_be() { + let mut ba: BitArray = Default::default(); + ba.write_word_be(BoundedInt::::max().into() - 2, 128); + assert( + ba.read_word_be(128).unwrap() == BoundedInt::::max().into() - 2, 'unexpected value' + ); +} + +#[test] +#[available_gas(20000000)] +fn test_write_word_be_half() { + let mut ba: BitArray = Default::default(); + ba.write_word_be(BoundedInt::::max().into() - 3, 64); + assert(ba.read_word_be(64).unwrap() == BoundedInt::::max().into() - 3, 'unexpected value'); +} + +#[test] +#[available_gas(20000000)] +fn test_write_word_le() { + let mut ba: BitArray = Default::default(); + ba.write_word_le(BoundedInt::::max().into() - 4, 128); + assert( + ba.read_word_le(128).unwrap() == BoundedInt::::max().into() - 4, 'unexpected value' + ); +} + +#[test] +#[available_gas(20000000)] +fn test_write_word_le_half() { + let mut ba: BitArray = Default::default(); + ba.write_word_le(BoundedInt::::max().into() - 5, 64); + assert(ba.read_word_le(64).unwrap() == BoundedInt::::max().into() - 5, 'unexpected value'); +} + +#[test] +#[available_gas(40000000)] +fn test_write_word_be_u256() { + let mut ba: BitArray = Default::default(); + let expected = u256 { low: BoundedInt::max() - 1, high: BoundedInt::max() - 2 }; + ba.write_word_be_u256(expected, 256); + assert(ba.read_word_be_u256(256).unwrap() == expected, 'unexpected value'); +} + +#[test] +#[available_gas(40000000)] +fn test_write_word_le_u256() { + let mut ba: BitArray = Default::default(); + let expected = u256 { low: BoundedInt::max() - 1, high: BoundedInt::max() - 2 }; + ba.write_word_le_u256(expected, 256); + assert(ba.read_word_le_u256(256).unwrap() == expected, 'unexpected value'); +} + +#[test] +#[available_gas(80000000)] +fn test_write_word_be_u512() { + let mut ba: BitArray = Default::default(); + let limb0 = BoundedInt::::max(); + let limb1 = BoundedInt::::max() - 1; + let limb2 = BoundedInt::::max() - 2; + let limb3 = BoundedInt::::max() - 3; + let expected = u512 { limb0, limb1, limb2, limb3 }; + ba.write_word_be_u512(expected, 512); + assert(ba.read_word_be_u512(512).unwrap() == expected, 'unexpected value'); +} + +#[test] +#[available_gas(80000000)] +fn test_write_word_le_u512() { + let mut ba: BitArray = Default::default(); + let limb0 = BoundedInt::::max(); + let limb1 = BoundedInt::::max() - 1; + let limb2 = BoundedInt::::max() - 2; + let limb3 = BoundedInt::::max() - 3; + let expected = u512 { limb0, limb1, limb2, limb3 }; + ba.write_word_le_u512(expected, 512); + assert(ba.read_word_le_u512(512).unwrap() == expected, 'unexpected value'); +} + +#[test] +#[available_gas(2000000000)] +fn test_stress_test() { + let mut ba: BitArray = Default::default(); + let mut index = 0; + let limit = 20; + loop { + if index == limit { + break; + } + let value: felt252 = index.into(); + ba.write_word_be(value, 248); + ba.write_word_le(value, 248); + index += 1; + }; + index = 0; + loop { + if index == limit { + break; + } + let value = ba.read_word_be(248).unwrap(); + assert(value == index.into(), 'not equal'); + let value = ba.read_word_le(248).unwrap(); + assert(value == index.into(), 'not equal'); + index += 1; + }; +} + +#[test] +#[available_gas(100000)] +fn test_serde_serialize() { + let mut out = array![]; + let ba = sample_bit_array(); + ba.serialize(ref out); + let length = out.pop_front().unwrap(); + let length: usize = length.try_into().unwrap(); + assert(length == ba.len(), 'len not equal'); + let data: felt252 = out.pop_front().unwrap(); + let expected: felt252 = BoundedInt::::max().into() - 1; + let expected = expected * one_shift_left_bytes_felt252(15); + assert(data == expected, 'unexpected data'); +} + +#[test] +#[available_gas(300000000)] +fn test_serde_ser_deser() { + let mut ba: BitArray = Default::default(); + let test: felt252 = 0x101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e; + ba.write_word_be(test, 248); + ba.write_word_be(test + 1, 248); + ba.write_word_be(test + 2, 248); + ba.write_word_be(test + 3, 248); + ba.write_word_be(test + 4, 248); + ba.write_word_be(test + 5, 248); + let mut out = array![]; + ba.serialize(ref out); + let mut span = out.span(); + let mut deser = Serde::::deserialize(ref span).unwrap(); + assert(deser.len() == ba.len(), 'expected equal lengths'); + assert(deser.read_word_be(248).unwrap() == test, 'expected test'); + assert(deser.read_word_be(248).unwrap() == test + 1, 'expected test + 1'); + assert(deser.read_word_be(248).unwrap() == test + 2, 'expected test + 2'); + assert(deser.read_word_be(248).unwrap() == test + 3, 'expected test + 3'); + assert(deser.read_word_be(248).unwrap() == test + 4, 'expected test + 4'); + assert(deser.read_word_be(248).unwrap() == test + 5, 'expected test + 5'); +} + +// helpers +fn sample_bit_array() -> BitArray { + let sample: felt252 = BoundedInt::::max().into() - 1; + let u256{low, high } = sample.into(); + let ba = BitArray { + data: array![], + current: low.into() * one_shift_left_bytes_felt252(15), + read_pos: 0, + write_pos: 8 * 16, + }; + ba +}