neo_solidity/
storage_key.rs

1//! Storage Key Computation Module
2//!
3//! Computes deterministic storage keys for Neo N3 smart contract state variables.
4//! This module implements the storage slot derivation algorithm compatible with
5//! Neo N3's storage model.
6//!
7//! # Key Derivation
8//!
9//! Storage keys are derived using SHA-256 hashing:
10//! - Simple variables: `SHA256(variable_name)`
11//! - Mappings: `SHA256(base_slot || encoded_key1 || encoded_key2 || ...)`
12//!
13//! # Key Types
14//!
15//! - [`KeyFragment`] - Represents a single mapping key (integer, address, bytes, etc.)
16//! - [`compute_state_slot`] - Computes base slot for a state variable
17//! - [`derive_mapping_slot`] - Derives slot for nested mapping access
18
19use num_bigint::BigInt;
20use num_traits::Signed;
21use sha2::{Digest, Sha256};
22
23/// Represents a single mapping key fragment used to derive a storage slot.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum KeyFragment {
26    Integer {
27        value: BigInt,
28        bits: u16,
29        signed: bool,
30    },
31    Boolean(bool),
32    Address(Vec<u8>),
33    Bytes(Vec<u8>),
34    String(String),
35}
36
37impl KeyFragment {
38    /// Create an integer key fragment
39    pub fn integer(value: BigInt, bits: u16, signed: bool) -> Self {
40        Self::Integer {
41            value,
42            bits,
43            signed,
44        }
45    }
46
47    /// Create a boolean key fragment
48    pub fn boolean(value: bool) -> Self {
49        Self::Boolean(value)
50    }
51
52    /// Create an address key fragment (20 bytes)
53    pub fn address(bytes: impl Into<Vec<u8>>) -> Self {
54        Self::Address(bytes.into())
55    }
56
57    /// Create a bytes key fragment
58    pub fn bytes(bytes: impl Into<Vec<u8>>) -> Self {
59        Self::Bytes(bytes.into())
60    }
61
62    /// Create a string key fragment
63    pub fn string(value: impl Into<String>) -> Self {
64        Self::String(value.into())
65    }
66
67    /// Create a uint256 key fragment
68    pub fn uint256(value: BigInt) -> Self {
69        Self::Integer {
70            value,
71            bits: 256,
72            signed: false,
73        }
74    }
75
76    /// Create an int256 key fragment
77    pub fn int256(value: BigInt) -> Self {
78        Self::Integer {
79            value,
80            bits: 256,
81            signed: true,
82        }
83    }
84
85    /// Get the type name of this fragment
86    pub fn type_name(&self) -> &'static str {
87        match self {
88            Self::Integer { signed: true, .. } => "int",
89            Self::Integer { signed: false, .. } => "uint",
90            Self::Boolean(_) => "bool",
91            Self::Address(_) => "address",
92            Self::Bytes(_) => "bytes",
93            Self::String(_) => "string",
94        }
95    }
96}
97
98/// Compute the canonical storage slot hash for a state variable name.
99///
100/// The returned value is the 32-byte SHA-256 digest of the UTF-8 name.
101pub fn compute_state_slot(name: &str) -> [u8; 32] {
102    let mut hasher = Sha256::new();
103    hasher.update(name.as_bytes());
104    let digest = hasher.finalize();
105    let mut out = [0u8; 32];
106    out.copy_from_slice(&digest);
107    out
108}
109
110/// Derive the storage slot for a mapping entry given the base slot and key fragments.
111///
112/// Each key fragment is serialised with a 4-byte little-endian length prefix to avoid
113/// ambiguity between `{a, bc}` and `{ab, c}` style encodings.
114pub fn derive_mapping_slot(base_slot: &[u8], fragments: &[KeyFragment]) -> [u8; 32] {
115    let mut buffer = Vec::new();
116    for fragment in fragments {
117        let encoded = encode_fragment(fragment);
118        buffer.extend_from_slice(&(encoded.len() as u32).to_le_bytes());
119        buffer.extend_from_slice(&encoded);
120    }
121    buffer.extend_from_slice(base_slot);
122
123    let digest = Sha256::digest(buffer);
124    let mut out = [0u8; 32];
125    out.copy_from_slice(&digest);
126    out
127}
128
129fn encode_fragment(fragment: &KeyFragment) -> Vec<u8> {
130    match fragment {
131        KeyFragment::Integer {
132            value,
133            bits,
134            signed,
135        } => encode_integer(value, *bits, *signed),
136        KeyFragment::Boolean(value) => vec![if *value { 1 } else { 0 }],
137        KeyFragment::Address(bytes) => bytes.clone(),
138        KeyFragment::Bytes(bytes) => bytes.clone(),
139        KeyFragment::String(value) => value.as_bytes().to_vec(),
140    }
141}
142
143fn encode_integer(value: &BigInt, bits: u16, signed: bool) -> Vec<u8> {
144    let min_bytes = (bits.max(8) as usize).div_ceil(8);
145    if signed {
146        let mut raw = value.to_signed_bytes_be();
147        let needs_negative_padding = value.is_negative();
148        let pad_byte = if needs_negative_padding { 0xFF } else { 0x00 };
149        if raw.len() < min_bytes {
150            let mut padded = vec![pad_byte; min_bytes];
151            padded[min_bytes - raw.len()..].copy_from_slice(&raw);
152            raw = padded;
153        }
154        raw
155    } else {
156        let (_, mut raw) = value.to_bytes_be();
157        let pad_byte = 0x00;
158        if raw.len() < min_bytes {
159            let mut padded = vec![pad_byte; min_bytes];
160            padded[min_bytes - raw.len()..].copy_from_slice(&raw);
161            raw = padded;
162        }
163        raw
164    }
165}
166
167#[cfg(test)]
168mod tests;