use hopper_runtime::error::ProgramError;
pub struct StateSnapshot<const SIZE: usize> {
data: [u8; SIZE],
len: usize,
truncated: bool,
}
impl<const SIZE: usize> StateSnapshot<SIZE> {
#[inline]
pub fn capture(data: &[u8]) -> Self {
let truncated = data.len() > SIZE;
let len = if truncated { SIZE } else { data.len() };
let mut snapshot = Self {
data: [0u8; SIZE],
len,
truncated,
};
let mut i = 0;
while i < len {
snapshot.data[i] = data[i];
i += 1;
}
snapshot
}
#[inline(always)]
pub fn len(&self) -> usize {
self.len
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline(always)]
pub fn was_truncated(&self) -> bool {
self.truncated
}
#[inline(always)]
pub fn data(&self) -> &[u8] {
&self.data[..self.len]
}
#[inline]
pub fn diff<'a>(&'a self, current: &'a [u8]) -> StateDiff<'a> {
let compare_len = if current.len() < self.len {
current.len()
} else {
self.len
};
StateDiff {
old: &self.data[..compare_len],
new: ¤t[..compare_len],
old_full_len: self.len,
new_full_len: current.len(),
}
}
#[inline]
pub fn has_changes(&self, current: &[u8]) -> bool {
if current.len() != self.len {
return true;
}
let mut i = 0;
while i < self.len {
if self.data[i] != current[i] {
return true;
}
i += 1;
}
false
}
#[inline]
pub fn range_changed(&self, current: &[u8], offset: usize, len: usize) -> bool {
let end = offset + len;
if end > self.len || end > current.len() {
return true; }
let mut i = offset;
while i < end {
if self.data[i] != current[i] {
return true;
}
i += 1;
}
false
}
#[inline]
pub fn restore_into(&self, target: &mut [u8]) -> Result<(), ProgramError> {
if target.len() < self.len {
return Err(ProgramError::AccountDataTooSmall);
}
let mut i = 0;
while i < self.len {
target[i] = self.data[i];
i += 1;
}
Ok(())
}
}
pub struct StateDiff<'a> {
old: &'a [u8],
new: &'a [u8],
old_full_len: usize,
new_full_len: usize,
}
impl<'a> StateDiff<'a> {
#[inline]
pub fn has_changes(&self) -> bool {
if self.old.len() != self.new.len() {
return true;
}
let mut i = 0;
while i < self.old.len() {
if self.old[i] != self.new[i] {
return true;
}
i += 1;
}
self.old_full_len != self.new_full_len
}
#[inline(always)]
pub fn was_resized(&self) -> bool {
self.old_full_len != self.new_full_len
}
#[inline(always)]
pub fn old_len(&self) -> usize {
self.old_full_len
}
#[inline(always)]
pub fn new_len(&self) -> usize {
self.new_full_len
}
#[inline]
pub fn field_changed(&self, offset: usize, size: usize) -> bool {
let end = offset + size;
if end > self.old.len() || end > self.new.len() {
return true;
}
let mut i = offset;
while i < end {
if self.old[i] != self.new[i] {
return true;
}
i += 1;
}
false
}
#[inline]
pub fn changed_byte_count(&self) -> usize {
let compare_len = if self.old.len() < self.new.len() {
self.old.len()
} else {
self.new.len()
};
let mut count = 0;
let mut i = 0;
while i < compare_len {
if self.old[i] != self.new[i] {
count += 1;
}
i += 1;
}
if self.old_full_len > self.new_full_len {
count += self.old_full_len - self.new_full_len;
} else {
count += self.new_full_len - self.old_full_len;
}
count
}
#[inline]
pub fn changed_regions<const MAX_REGIONS: usize>(&self) -> ChangedRegions<MAX_REGIONS> {
let compare_len = if self.old.len() < self.new.len() {
self.old.len()
} else {
self.new.len()
};
let mut regions = ChangedRegions {
entries: [ChangedRegion {
offset: 0,
length: 0,
}; MAX_REGIONS],
count: 0,
};
let mut i = 0;
while i < compare_len && regions.count < MAX_REGIONS {
if self.old[i] != self.new[i] {
let start = i;
while i < compare_len && self.old[i] != self.new[i] {
i += 1;
}
regions.entries[regions.count] = ChangedRegion {
offset: start,
length: i - start,
};
regions.count += 1;
} else {
i += 1;
}
}
regions
}
}
#[derive(Clone, Copy)]
pub struct ChangedRegion {
pub offset: usize,
pub length: usize,
}
pub struct ChangedRegions<const N: usize> {
entries: [ChangedRegion; N],
count: usize,
}
impl<const N: usize> ChangedRegions<N> {
#[inline(always)]
pub fn len(&self) -> usize {
self.count
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline(always)]
pub fn get(&self, index: usize) -> Option<&ChangedRegion> {
if index < self.count {
Some(&self.entries[index])
} else {
None
}
}
#[inline]
pub fn iter(&self) -> ChangedRegionIter<'_> {
ChangedRegionIter {
entries: &self.entries[..self.count],
pos: 0,
}
}
}
pub struct ChangedRegionIter<'a> {
entries: &'a [ChangedRegion],
pos: usize,
}
impl<'a> Iterator for ChangedRegionIter<'a> {
type Item = &'a ChangedRegion;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.entries.len() {
return None;
}
let item = &self.entries[self.pos];
self.pos += 1;
Some(item)
}
}
#[inline]
pub fn field_diff_mask(old: &[u8], new: &[u8], fields: &[(&str, usize, usize)]) -> u64 {
let mut mask: u64 = 0;
let mut i = 0;
while i < fields.len() && i < 64 {
let (_, offset, size) = fields[i];
let end = offset + size;
if end <= old.len() && end <= new.len() {
let mut j = offset;
while j < end {
if old[j] != new[j] {
mask |= 1u64 << i;
break;
}
j += 1;
}
} else {
mask |= 1u64 << i; }
i += 1;
}
mask
}