1use crate::StorageSlot;
2
3use educe::Educe;
4use fuel_crypto::Hasher;
5use fuel_merkle::{
6 binary::root_calculator::MerkleRootCalculator as BinaryMerkleTree,
7 sparse::{
8 MerkleTreeKey,
9 in_memory::MerkleTree as SparseMerkleTree,
10 },
11};
12use fuel_types::{
13 Bytes32,
14 ContractId,
15 Salt,
16};
17
18use alloc::vec::Vec;
19use core::{
20 borrow::Borrow,
21 iter,
22};
23use fuel_types::bytes::Bytes;
24
25const LEAF_SIZE: usize = 16 * 1024;
30const PADDING_BYTE: u8 = 0u8;
34const MULTIPLE: usize = 8;
35
36#[derive(Default, Clone, PartialEq, Eq, Hash, Educe)]
37#[educe(Debug)]
38#[derive(
39 serde::Serialize,
40 serde::Deserialize,
41 fuel_types::canonical::Deserialize,
42 fuel_types::canonical::Serialize,
43)]
44pub struct Contract(Bytes);
46
47impl Contract {
48 pub const EMPTY_CONTRACT_ID: ContractId = ContractId::new([
51 55, 187, 13, 108, 165, 51, 58, 230, 74, 109, 215, 229, 33, 69, 82, 120, 81, 4,
52 85, 54, 172, 30, 84, 115, 226, 164, 0, 99, 103, 189, 154, 243,
53 ]);
54
55 pub fn len(&self) -> usize {
57 self.0.len()
58 }
59
60 pub fn is_empty(&self) -> bool {
62 self.0.is_empty()
63 }
64
65 pub fn root(&self) -> Bytes32 {
67 Self::root_from_code(self)
68 }
69
70 pub fn root_from_code<B>(bytes: B) -> Bytes32
74 where
75 B: AsRef<[u8]>,
76 {
77 let mut tree = BinaryMerkleTree::new();
78 bytes.as_ref().chunks(LEAF_SIZE).for_each(|leaf| {
79 let len = leaf.len();
83 if len == LEAF_SIZE || len % MULTIPLE == 0 {
84 tree.push(leaf);
85 } else {
86 let padding_size = len.next_multiple_of(MULTIPLE);
87 let mut padded_leaf = [PADDING_BYTE; LEAF_SIZE];
88 padded_leaf[0..len].clone_from_slice(leaf);
89 tree.push(padded_leaf[..padding_size].as_ref());
90 }
91 });
92
93 tree.root().into()
94 }
95
96 pub fn initial_state_root<'a, I>(storage_slots: I) -> Bytes32
98 where
99 I: Iterator<Item = &'a StorageSlot>,
100 {
101 let storage_slots = storage_slots
102 .map(|slot| (*slot.key(), slot.value()))
103 .map(|(key, data)| (MerkleTreeKey::new(key), data));
104 let root = SparseMerkleTree::root_from_set(storage_slots);
105 root.into()
106 }
107
108 pub fn default_state_root() -> Bytes32 {
110 Self::initial_state_root(iter::empty())
111 }
112
113 pub fn id(salt: &Salt, root: &Bytes32, state_root: &Bytes32) -> ContractId {
117 let mut hasher = Hasher::default();
118
119 hasher.input(ContractId::SEED);
120 hasher.input(salt);
121 hasher.input(root);
122 hasher.input(state_root);
123
124 ContractId::from(*hasher.digest())
125 }
126}
127
128impl From<Vec<u8>> for Contract {
129 fn from(c: Vec<u8>) -> Self {
130 Self(c.into())
131 }
132}
133
134impl From<Contract> for Vec<u8> {
135 fn from(c: Contract) -> Vec<u8> {
136 c.0.into_inner()
137 }
138}
139
140impl From<&[u8]> for Contract {
141 fn from(c: &[u8]) -> Self {
142 Self(c.to_vec().into())
143 }
144}
145
146impl From<&mut [u8]> for Contract {
147 fn from(c: &mut [u8]) -> Self {
148 Self(c.to_vec().into())
149 }
150}
151
152impl Borrow<[u8]> for Contract {
153 fn borrow(&self) -> &[u8] {
154 self.0.borrow()
155 }
156}
157
158impl AsRef<[u8]> for Contract {
159 fn as_ref(&self) -> &[u8] {
160 self.0.as_ref()
161 }
162}
163
164impl AsMut<[u8]> for Contract {
165 fn as_mut(&mut self) -> &mut [u8] {
166 self.0.as_mut()
167 }
168}
169
170#[allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)]
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use fuel_types::{
175 Bytes64,
176 bytes::WORD_SIZE,
177 };
178 use itertools::Itertools;
179 use quickcheck_macros::quickcheck;
180 use rand::{
181 RngCore,
182 SeedableRng,
183 rngs::StdRng,
184 };
185 use rstest::rstest;
186
187 #[rstest]
190 fn code_root_snapshot(
191 #[values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100)] instructions: usize,
192 ) {
193 let mut rng = StdRng::seed_from_u64(100);
194 let code_len = instructions * WORD_SIZE / 2;
195 let mut code = alloc::vec![0u8; code_len];
196 rng.fill_bytes(code.as_mut_slice());
197
198 let root = Contract::root_from_code(code);
200
201 insta::with_settings!(
203 {snapshot_suffix => format!("instructions-{instructions}")},
204 {
205 insta::assert_debug_snapshot!(root);
206 }
207 );
208 }
209
210 #[quickcheck]
212 fn contract_root_matches_code_root(instructions: u8) -> bool {
213 let mut rng = StdRng::seed_from_u64(100);
214 let code_len = instructions as usize * WORD_SIZE / 2;
215 let mut code = alloc::vec![0u8; code_len];
216 rng.fill_bytes(code.as_mut_slice());
217 let contract = Contract::from(code.clone());
218 let code_root = Contract::root_from_code(code);
220 let contract_root = contract.root();
221 code_root == contract_root
222 }
223
224 #[rstest]
225 fn state_root_snapshot(
226 #[values(Vec::new(), vec![Bytes64::new([1u8; 64])])] state_slot_bytes: Vec<
227 Bytes64,
228 >,
229 ) {
230 let slots: Vec<StorageSlot> =
231 state_slot_bytes.iter().map(Into::into).collect_vec();
232 let state_root = Contract::initial_state_root(&mut slots.iter());
233 insta::with_settings!(
235 {snapshot_suffix => format!("state-root-{}", slots.len())},
236 {
237 insta::assert_debug_snapshot!(state_root);
238 }
239 );
240 }
241
242 #[test]
243 fn default_state_root_snapshot() {
244 let default_root = Contract::default_state_root();
245 insta::assert_debug_snapshot!(default_root);
246 }
247
248 #[test]
249 fn multi_leaf_state_root_snapshot() {
250 let mut rng = StdRng::seed_from_u64(0xF00D);
251 let partial_leaf_size = 4;
253 let code_len = 5 * LEAF_SIZE + partial_leaf_size;
254 let mut code = alloc::vec![0u8; code_len];
255 rng.fill_bytes(code.as_mut_slice());
256
257 let root = Contract::root_from_code(code);
259
260 insta::with_settings!(
262 {snapshot_suffix => "multi-leaf-state-root"},
263 {
264 insta::assert_debug_snapshot!(root);
265 }
266 );
267 }
268
269 #[rstest]
270 #[case(0)]
271 #[case(1)]
272 #[case(8)]
273 #[case(500)]
274 #[case(1000)]
275 #[case(1024)]
276 #[case(1025)]
277 fn partial_leaf_state_root(#[case] partial_leaf_size: usize) {
278 let mut rng = StdRng::seed_from_u64(0xF00D);
279 let code_len = partial_leaf_size;
280 let mut code = alloc::vec![0u8; code_len];
281 rng.fill_bytes(code.as_mut_slice());
282
283 let root = Contract::root_from_code(code.clone());
285
286 let expected_root = {
288 let mut tree = BinaryMerkleTree::new();
289
290 let sz = partial_leaf_size.next_multiple_of(8);
296 if sz > 0 {
297 let mut padded_leaf = vec![PADDING_BYTE; sz];
298 padded_leaf[0..code_len].clone_from_slice(&code);
299 tree.push(&padded_leaf);
300 }
301 tree.root().into()
302 };
303
304 assert_eq!(root, expected_root);
305 }
306
307 #[rstest]
308 #[case(0)]
309 #[case(1)]
310 #[case(8)]
311 #[case(500)]
312 #[case(1000)]
313 #[case(1024)]
314 #[case(1025)]
315 fn multi_leaf_state_root(#[case] partial_leaf_size: usize) {
316 let mut rng = StdRng::seed_from_u64(0xF00D);
317 let code_len = 3 * LEAF_SIZE + partial_leaf_size;
319 let mut code = alloc::vec![0u8; code_len];
320 rng.fill_bytes(code.as_mut_slice());
321
322 let root = Contract::root_from_code(code.clone());
324
325 let expected_root = {
327 let mut tree = BinaryMerkleTree::new();
328
329 let leaves = code.chunks(LEAF_SIZE).collect::<Vec<_>>();
330 tree.push(leaves[0]);
331 tree.push(leaves[1]);
332 tree.push(leaves[2]);
333
334 let sz = partial_leaf_size.next_multiple_of(8);
340 if sz > 0 {
341 let mut padded_leaf = vec![PADDING_BYTE; sz];
342 padded_leaf[0..partial_leaf_size].clone_from_slice(leaves[3]);
343 tree.push(&padded_leaf);
344 }
345 tree.root().into()
346 };
347
348 assert_eq!(root, expected_root);
349 }
350
351 #[test]
352 fn empty_contract_id() {
353 let contract = Contract::from(vec![]);
354 let salt = Salt::zeroed();
355 let root = contract.root();
356 let state_root = Contract::default_state_root();
357
358 let calculated_id = Contract::id(&salt, &root, &state_root);
359 assert_eq!(calculated_id, Contract::EMPTY_CONTRACT_ID)
360 }
361}