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