Skip to main content

fuel_tx/
contract.rs

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
25/// The target size of Merkle tree leaves in bytes. Contract code will will be divided
26/// into chunks of this size and pushed to the Merkle tree.
27///
28/// See https://github.com/FuelLabs/fuel-specs/blob/master/src/identifiers/contract-id.md
29const LEAF_SIZE: usize = 16 * 1024;
30/// In the event that contract code cannot be divided evenly by the `LEAF_SIZE`, the
31/// remainder must be padded to the nearest multiple of 8 bytes. Padding is achieved by
32/// repeating the `PADDING_BYTE`.
33const 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)]
44/// Deployable representation of a contract code.
45pub struct Contract(Bytes);
46
47impl Contract {
48    /// The `ContractId` of the contract with empty bytecode, zero salt, and empty state
49    /// root.
50    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    /// Number of bytes in the contract's bytecode
56    pub fn len(&self) -> usize {
57        self.0.len()
58    }
59
60    /// Check if the contract's bytecode is empty
61    pub fn is_empty(&self) -> bool {
62        self.0.is_empty()
63    }
64
65    /// Calculate the code root of the contract, using [`Self::root_from_code`].
66    pub fn root(&self) -> Bytes32 {
67        Self::root_from_code(self)
68    }
69
70    /// Calculate the code root from a contract.
71    ///
72    /// <https://github.com/FuelLabs/fuel-specs/blob/master/src/identifiers/contract-id.md>
73    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            // If the bytecode is not a multiple of LEAF_SIZE, the final leaf
80            // should be zero-padded rounding up to the nearest multiple of 8
81            // bytes.
82            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    /// Calculate the root of the initial storage slots for this contract
97    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    /// The default state root value without any entries
109    pub fn default_state_root() -> Bytes32 {
110        Self::initial_state_root(iter::empty())
111    }
112
113    /// Calculate and return the contract id, provided a salt, code root and state root.
114    ///
115    /// <https://github.com/FuelLabs/fuel-specs/blob/master/src/identifiers/contract-id.md>
116    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    // safe-guard against breaking changes to the code root calculation for valid
188    // sizes of bytecode (multiples of instruction size in bytes (half-word))
189    #[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        // compute root
199        let root = Contract::root_from_code(code);
200
201        // take root snapshot
202        insta::with_settings!(
203            {snapshot_suffix => format!("instructions-{instructions}")},
204            {
205                insta::assert_debug_snapshot!(root);
206            }
207        );
208    }
209
210    // validate code_root is always equivalent to contract.root
211    #[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        // compute root
219        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        // take root snapshot
234        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        // 5 full leaves and a partial 6th leaf with 4 bytes of data
252        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        // compute root
258        let root = Contract::root_from_code(code);
259
260        // take root snapshot
261        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        // Compute root
284        let root = Contract::root_from_code(code.clone());
285
286        // Compute expected root
287        let expected_root = {
288            let mut tree = BinaryMerkleTree::new();
289
290            // Push partial leaf with manual padding.
291            // We start by generating an n-byte array, where n is the code
292            // length rounded to the nearest multiple of 8, and each byte is the
293            // PADDING_BYTE by default. The leaf is generated by copying the
294            // remaining data bytes into the start of this array.
295            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        // 3 full leaves and a partial 4th leaf
318        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        // Compute root
323        let root = Contract::root_from_code(code.clone());
324
325        // Compute expected root
326        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            // Push partial leaf with manual padding.
335            // We start by generating an n-byte array, where n is the code
336            // length rounded to the nearest multiple of 8, and each byte is the
337            // PADDING_BYTE by default. The leaf is generated by copying the
338            // remaining data bytes into the start of this array.
339            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}