unc_primitives/
state.rs

1use borsh::{BorshDeserialize, BorshSerialize};
2
3use unc_primitives_core::hash::{hash, CryptoHash};
4
5/// State value reference. Used to charge fees for value length before retrieving the value itself.
6#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Eq, Hash)]
7pub struct ValueRef {
8    /// Value length in bytes.
9    pub length: u32,
10    /// Unique value hash.
11    pub hash: CryptoHash,
12}
13
14impl ValueRef {
15    /// Create serialized value reference by the value.
16    /// Resulting array stores 4 bytes of length and then 32 bytes of hash.
17    /// TODO (#7327): consider passing hash here to avoid double computation
18    pub fn new(value: &[u8]) -> Self {
19        Self { length: value.len() as u32, hash: hash(value) }
20    }
21
22    /// Decode value reference from the raw byte array.
23    pub fn decode(bytes: &[u8; 36]) -> Self {
24        let (length, hash) = stdx::split_array(bytes);
25        let length = u32::from_le_bytes(*length);
26        ValueRef { length, hash: CryptoHash(*hash) }
27    }
28
29    /// Returns length of the referenced value.
30    pub fn len(&self) -> usize {
31        usize::try_from(self.length).unwrap()
32    }
33}
34
35impl std::cmp::PartialEq<[u8]> for ValueRef {
36    fn eq(&self, rhs: &[u8]) -> bool {
37        self.len() == rhs.len() && self.hash == CryptoHash::hash_bytes(rhs)
38    }
39}
40
41impl std::fmt::Debug for ValueRef {
42    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        write!(fmt, "({}, {})", self.length, self.hash)
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use crate::state::ValueRef;
50    use unc_primitives_core::hash::hash;
51
52    #[test]
53    fn test_encode_decode() {
54        let value = vec![1, 2, 3];
55        let old_value_ref = ValueRef::new(&value);
56        let mut value_ref_ser = [0u8; 36];
57        value_ref_ser[0..4].copy_from_slice(&old_value_ref.length.to_le_bytes());
58        value_ref_ser[4..36].copy_from_slice(&old_value_ref.hash.0);
59        let value_ref = ValueRef::decode(&value_ref_ser);
60        assert_eq!(value_ref.length, value.len() as u32);
61        assert_eq!(value_ref.hash, hash(&value));
62    }
63}
64
65#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq)]
66pub enum FlatStateValue {
67    Ref(ValueRef),
68    Inlined(Vec<u8>),
69}
70
71impl FlatStateValue {
72    /// Defines value size threshold for flat state inlining.
73    /// It means that values having size greater than the threshold will be stored
74    /// in FlatState as `FlatStateValue::Ref`, otherwise the whole value will be
75    /// stored as `FlatStateValue::Inlined`.
76    /// See the following comment for reasoning behind the threshold value:
77    /// https://github.com/utnet-org/utility/issues/8243#issuecomment-1523049994
78    pub const INLINE_DISK_VALUE_THRESHOLD: usize = 4000;
79
80    pub fn on_disk(value: &[u8]) -> Self {
81        if value.len() <= Self::INLINE_DISK_VALUE_THRESHOLD {
82            Self::inlined(value)
83        } else {
84            Self::value_ref(value)
85        }
86    }
87
88    pub fn value_ref(value: &[u8]) -> Self {
89        Self::Ref(ValueRef::new(value))
90    }
91
92    pub fn inlined(value: &[u8]) -> Self {
93        Self::Inlined(value.to_vec())
94    }
95
96    pub fn to_value_ref(&self) -> ValueRef {
97        match self {
98            Self::Ref(value_ref) => value_ref.clone(),
99            Self::Inlined(value) => ValueRef::new(value),
100        }
101    }
102
103    pub fn value_len(&self) -> usize {
104        match self {
105            Self::Ref(value_ref) => value_ref.len(),
106            Self::Inlined(value) => value.len(),
107        }
108    }
109}