fuel_tx/
contract.rs

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
26/// The target size of Merkle tree leaves in bytes. Contract code will will be divided
27/// into chunks of this size and pushed to the Merkle tree.
28///
29/// See https://github.com/FuelLabs/fuel-specs/blob/master/src/identifiers/contract-id.md
30const LEAF_SIZE: usize = 16 * 1024;
31/// In the event that contract code cannot be divided evenly by the `LEAF_SIZE`, the
32/// remainder must be padded to the nearest multiple of 8 bytes. Padding is achieved by
33/// repeating the `PADDING_BYTE`.
34const 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)]
45/// Deployable representation of a contract code.
46pub struct Contract(#[educe(Debug(method(fmt_truncated_hex::<16>)))] Vec<u8>);
47
48impl Contract {
49    /// The `ContractId` of the contract with empty bytecode, zero salt, and empty state
50    /// root.
51    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    /// Number of bytes in the contract's bytecode
57    pub fn len(&self) -> usize {
58        self.0.len()
59    }
60
61    /// Check if the contract's bytecode is empty
62    pub fn is_empty(&self) -> bool {
63        self.0.is_empty()
64    }
65
66    /// Calculate the code root of the contract, using [`Self::root_from_code`].
67    pub fn root(&self) -> Bytes32 {
68        Self::root_from_code(self)
69    }
70
71    /// Calculate the code root from a contract.
72    ///
73    /// <https://github.com/FuelLabs/fuel-specs/blob/master/src/identifiers/contract-id.md>
74    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            // If the bytecode is not a multiple of LEAF_SIZE, the final leaf
81            // should be zero-padded rounding up to the nearest multiple of 8
82            // bytes.
83            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    /// Calculate the root of the initial storage slots for this contract
98    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    /// The default state root value without any entries
110    pub fn default_state_root() -> Bytes32 {
111        Self::initial_state_root(iter::empty())
112    }
113
114    /// Calculate and return the contract id, provided a salt, code root and state root.
115    ///
116    /// <https://github.com/FuelLabs/fuel-specs/blob/master/src/identifiers/contract-id.md>
117    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    // safe-guard against breaking changes to the code root calculation for valid
196    // sizes of bytecode (multiples of instruction size in bytes (half-word))
197    #[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        // compute root
207        let root = Contract::root_from_code(code);
208
209        // take root snapshot
210        insta::with_settings!(
211            {snapshot_suffix => format!("instructions-{instructions}")},
212            {
213                insta::assert_debug_snapshot!(root);
214            }
215        );
216    }
217
218    // validate code_root is always equivalent to contract.root
219    #[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        // compute root
227        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        // take root snapshot
242        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        // 5 full leaves and a partial 6th leaf with 4 bytes of data
260        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        // compute root
266        let root = Contract::root_from_code(code);
267
268        // take root snapshot
269        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        // Compute root
292        let root = Contract::root_from_code(code.clone());
293
294        // Compute expected root
295        let expected_root = {
296            let mut tree = BinaryMerkleTree::new();
297
298            // Push partial leaf with manual padding.
299            // We start by generating an n-byte array, where n is the code
300            // length rounded to the nearest multiple of 8, and each byte is the
301            // PADDING_BYTE by default. The leaf is generated by copying the
302            // remaining data bytes into the start of this array.
303            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        // 3 full leaves and a partial 4th leaf
326        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        // Compute root
331        let root = Contract::root_from_code(code.clone());
332
333        // Compute expected root
334        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            // Push partial leaf with manual padding.
343            // We start by generating an n-byte array, where n is the code
344            // length rounded to the nearest multiple of 8, and each byte is the
345            // PADDING_BYTE by default. The leaf is generated by copying the
346            // remaining data bytes into the start of this array.
347            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}