Coverage Report

Created: 2024-09-17 14:02

/home/runner/actions-runner/_work/fuel-vm/fuel-vm/fuel-tx/src/contract.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::{
2
    StorageSlot,
3
    Transaction,
4
    ValidityError,
5
};
6
7
use derivative::Derivative;
8
use fuel_crypto::Hasher;
9
use fuel_merkle::{
10
    binary::root_calculator::MerkleRootCalculator as BinaryMerkleTree,
11
    sparse::{
12
        in_memory::MerkleTree as SparseMerkleTree,
13
        MerkleTreeKey,
14
    },
15
};
16
use fuel_types::{
17
    fmt_truncated_hex,
18
    Bytes32,
19
    ContractId,
20
    Salt,
21
};
22
23
use alloc::vec::Vec;
24
use 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
30
const 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`.
34
const PADDING_BYTE: u8 = 0u8;
35
const MULTIPLE: usize = 8;
36
37
#[derive(Default, Derivative, Clone, PartialEq, Eq, Hash)]
38
0
#[derivative(Debug)]
39
0
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40
0
#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
41
0
/// Deployable representation of a contract code.
42
0
pub struct Contract(
43
0
    #[derivative(Debug(format_with = "fmt_truncated_hex::<16>"))] Vec<u8>,
44
0
);
45
46
impl Contract {
47
    /// The `ContractId` of the contract with empty bytecode, zero salt, and empty state
48
    /// root.
49
    pub const EMPTY_CONTRACT_ID: ContractId = ContractId::new([
50
        55, 187, 13, 108, 165, 51, 58, 230, 74, 109, 215, 229, 33, 69, 82, 120, 81, 4,
51
        85, 54, 172, 30, 84, 115, 226, 164, 0, 99, 103, 189, 154, 243,
52
    ]);
53
54
    /// Number of bytes in the contract's bytecode
55
0
    pub fn len(&self) -> usize {
56
0
        self.0.len()
57
0
    }
58
59
    /// Check if the contract's bytecode is empty
60
0
    pub fn is_empty(&self) -> bool {
61
0
        self.0.is_empty()
62
0
    }
63
64
    /// Calculate the code root of the contract, using [`Self::root_from_code`].
65
1.47k
    pub fn root(&self) -> Bytes32 {
66
1.47k
        Self::root_from_code(self)
67
1.47k
    }
68
69
    /// Calculate the code root from a contract.
70
    ///
71
    /// <https://github.com/FuelLabs/fuel-specs/blob/master/src/identifiers/contract-id.md>
72
5.77k
    pub fn root_from_code<B>(bytes: B) -> Bytes32
73
5.77k
    where
74
5.77k
        B: AsRef<[u8]>,
75
5.77k
    {
76
5.77k
        let mut tree = BinaryMerkleTree::new();
77
5.77k
        bytes.as_ref().chunks(LEAF_SIZE).for_each(|leaf| {
78
5.77k
            // If the bytecode is not a multiple of LEAF_SIZE, the final leaf
79
5.77k
            // should be zero-padded rounding up to the nearest multiple of 8
80
5.77k
            // bytes.
81
5.77k
            let len = leaf.len();
82
5.77k
            if len == LEAF_SIZE || 
len % MULTIPLE == 05.74k
{
  Branch (82:16): [True: 26, False: 1.12k]
  Branch (82:36): [True: 62, False: 1.06k]
  Branch (82:16): [True: 8, False: 353]
  Branch (82:36): [True: 67, False: 286]
  Branch (82:16): [True: 0, False: 2.48k]
  Branch (82:36): [True: 2.37k, False: 117]
  Branch (82:16): [True: 0, False: 1.09k]
  Branch (82:36): [True: 419, False: 678]
  Branch (82:16): [True: 0, False: 634]
  Branch (82:36): [True: 315, False: 319]
  Branch (82:16): [True: 0, False: 46]
  Branch (82:36): [True: 19, False: 27]
83
3.28k
                tree.push(leaf);
84
3.28k
            } else {
85
2.49k
                let padding_size = len.next_multiple_of(MULTIPLE);
86
2.49k
                let mut padded_leaf = [PADDING_BYTE; LEAF_SIZE];
87
2.49k
                padded_leaf[0..len].clone_from_slice(leaf);
88
2.49k
                tree.push(padded_leaf[..padding_size].as_ref());
89
2.49k
            }
90
5.77k
        });
91
5.77k
92
5.77k
        tree.root().into()
93
5.77k
    }
94
95
    /// Calculate the root of the initial storage slots for this contract
96
1.42k
    pub fn initial_state_root<'a, I>(storage_slots: I) -> Bytes32
97
1.42k
    where
98
1.42k
        I: Iterator<Item = &'a StorageSlot>,
99
1.42k
    {
100
1.42k
        let storage_slots = storage_slots
101
4.14k
            .map(|slot| (*slot.key(), slot.value()))
102
4.14k
            .map(|(key, data)| (MerkleTreeKey::new(key), data));
103
1.42k
        let root = SparseMerkleTree::root_from_set(storage_slots);
104
1.42k
        root.into()
105
1.42k
    }
106
107
    /// The default state root value without any entries
108
44
    pub fn default_state_root() -> Bytes32 {
109
44
        Self::initial_state_root(iter::empty())
110
44
    }
111
112
    /// Calculate and return the contract id, provided a salt, code root and state root.
113
    ///
114
    /// <https://github.com/FuelLabs/fuel-specs/blob/master/src/identifiers/contract-id.md>
115
1.41k
    pub fn id(&self, salt: &Salt, root: &Bytes32, state_root: &Bytes32) -> ContractId {
116
1.41k
        let mut hasher = Hasher::default();
117
1.41k
118
1.41k
        hasher.input(ContractId::SEED);
119
1.41k
        hasher.input(salt);
120
1.41k
        hasher.input(root);
121
1.41k
        hasher.input(state_root);
122
1.41k
123
1.41k
        ContractId::from(*hasher.digest())
124
1.41k
    }
125
}
126
127
impl From<Vec<u8>> for Contract {
128
105
    fn from(c: Vec<u8>) -> Self {
129
105
        Self(c)
130
105
    }
131
}
132
133
impl From<&[u8]> for Contract {
134
2.00k
    fn from(c: &[u8]) -> Self {
135
2.00k
        Self(c.into())
136
2.00k
    }
137
}
138
139
impl From<&mut [u8]> for Contract {
140
0
    fn from(c: &mut [u8]) -> Self {
141
0
        Self(c.into())
142
0
    }
143
}
144
145
impl From<Contract> for Vec<u8> {
146
0
    fn from(c: Contract) -> Vec<u8> {
147
0
        c.0
148
0
    }
149
}
150
151
impl AsRef<[u8]> for Contract {
152
23.3k
    fn as_ref(&self) -> &[u8] {
153
23.3k
        self.0.as_ref()
154
23.3k
    }
155
}
156
157
impl AsMut<[u8]> for Contract {
158
0
    fn as_mut(&mut self) -> &mut [u8] {
159
0
        self.0.as_mut()
160
0
    }
161
}
162
163
impl TryFrom<&Transaction> for Contract {
164
    type Error = ValidityError;
165
166
0
    fn try_from(tx: &Transaction) -> Result<Self, Self::Error> {
167
0
        match tx {
168
0
            Transaction::Create(create) => TryFrom::try_from(create),
169
            _ => {
170
0
                Err(ValidityError::TransactionOutputContainsContractCreated { index: 0 })
171
            }
172
        }
173
0
    }
174
}
175
176
#[allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)]
177
#[cfg(test)]
178
mod tests {
179
    use super::*;
180
    use fuel_types::{
181
        bytes::WORD_SIZE,
182
        Bytes64,
183
    };
184
    use itertools::Itertools;
185
    use quickcheck_macros::quickcheck;
186
    use rand::{
187
        rngs::StdRng,
188
        RngCore,
189
        SeedableRng,
190
    };
191
    use rstest::rstest;
192
193
    // safe-guard against breaking changes to the code root calculation for valid
194
    // sizes of bytecode (multiples of instruction size in bytes (half-word))
195
11
    #[rstest]
196
11
    fn code_root_snapshot(
197
11
        #[values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100)] instructions: usize,
198
11
    ) {
199
11
        let mut rng = StdRng::seed_from_u64(100);
200
11
        let code_len = instructions * WORD_SIZE / 2;
201
11
        let mut code = alloc::vec![0u8; code_len];
202
11
        rng.fill_bytes(code.as_mut_slice());
203
11
204
11
        // compute root
205
11
        let root = Contract::root_from_code(code);
206
207
        // take root snapshot
208
        insta::with_settings!(
209
            {snapshot_suffix => format!("instructions-{instructions}")},
210
            {
211
                insta::assert_debug_snapshot!(root);
212
            }
213
        );
214
11
    }
215
216
    // validate code_root is always equivalent to contract.root
217
1
    #[quickcheck]
218
100
    fn contract_root_matches_code_root(instructions: u8) -> bool {
219
100
        let mut rng = StdRng::seed_from_u64(100);
220
100
        let code_len = instructions as usize * WORD_SIZE / 2;
221
100
        let mut code = alloc::vec![0u8; code_len];
222
100
        rng.fill_bytes(code.as_mut_slice());
223
100
        let contract = Contract::from(code.clone());
224
100
        // compute root
225
100
        let code_root = Contract::root_from_code(code);
226
100
        let contract_root = contract.root();
227
100
        code_root == contract_root
228
100
    }
229
230
2
    #[rstest]
231
2
    fn state_root_snapshot(
232
2
        #[values(Vec::new(), vec![Bytes64::new([1u8; 64])])] state_slot_bytes: Vec<
233
2
            Bytes64,
234
2
        >,
235
2
    ) {
236
2
        let slots: Vec<StorageSlot> =
237
2
            state_slot_bytes.iter().map(Into::into).collect_vec();
238
2
        let state_root = Contract::initial_state_root(&mut slots.iter());
239
        // take root snapshot
240
        insta::with_settings!(
241
            {snapshot_suffix => format!("state-root-{}", slots.len())},
242
            {
243
                insta::assert_debug_snapshot!(state_root);
244
            }
245
        );
246
2
    }
247
248
    #[test]
249
1
    fn default_state_root_snapshot() {
250
1
        let default_root = Contract::default_state_root();
251
1
        insta::assert_debug_snapshot!(default_root);
252
1
    }
253
254
    #[test]
255
1
    fn multi_leaf_state_root_snapshot() {
256
1
        let mut rng = StdRng::seed_from_u64(0xF00D);
257
1
        // 5 full leaves and a partial 6th leaf with 4 bytes of data
258
1
        let partial_leaf_size = 4;
259
1
        let code_len = 5 * LEAF_SIZE + partial_leaf_size;
260
1
        let mut code = alloc::vec![0u8; code_len];
261
1
        rng.fill_bytes(code.as_mut_slice());
262
1
263
1
        // compute root
264
1
        let root = Contract::root_from_code(code);
265
266
        // take root snapshot
267
        insta::with_settings!(
268
            {snapshot_suffix => "multi-leaf-state-root"},
269
            {
270
                insta::assert_debug_snapshot!(root);
271
            }
272
        );
273
1
    }
274
275
7
    #[rstest]
276
    #[case(0)]
277
    #[case(1)]
278
    #[case(8)]
279
    #[case(500)]
280
    #[case(1000)]
281
    #[case(1024)]
282
    #[case(1025)]
283
7
    fn partial_leaf_state_root(#[case] partial_leaf_size: usize) {
284
7
        let mut rng = StdRng::seed_from_u64(0xF00D);
285
7
        let code_len = partial_leaf_size;
286
7
        let mut code = alloc::vec![0u8; code_len];
287
7
        rng.fill_bytes(code.as_mut_slice());
288
7
289
7
        // Compute root
290
7
        let root = Contract::root_from_code(code.clone());
291
292
        // Compute expected root
293
7
        let expected_root = {
294
7
            let mut tree = BinaryMerkleTree::new();
295
7
296
7
            // Push partial leaf with manual padding.
297
7
            // We start by generating an n-byte array, where n is the code
298
7
            // length rounded to the nearest multiple of 8, and each byte is the
299
7
            // PADDING_BYTE by default. The leaf is generated by copying the
300
7
            // remaining data bytes into the start of this array.
301
7
            let sz = partial_leaf_size.next_multiple_of(8);
302
7
            if sz > 0 {
  Branch (302:16): [True: 6, False: 1]
303
6
                let mut padded_leaf = vec![PADDING_BYTE; sz];
304
6
                padded_leaf[0..code_len].clone_from_slice(&code);
305
6
                tree.push(&padded_leaf);
306
6
            }
1
307
7
            tree.root().into()
308
7
        };
309
7
310
7
        assert_eq!(root, expected_root);
311
7
    }
312
313
7
    #[rstest]
314
    #[case(0)]
315
    #[case(1)]
316
    #[case(8)]
317
    #[case(500)]
318
    #[case(1000)]
319
    #[case(1024)]
320
    #[case(1025)]
321
7
    fn multi_leaf_state_root(#[case] partial_leaf_size: usize) {
322
7
        let mut rng = StdRng::seed_from_u64(0xF00D);
323
7
        // 3 full leaves and a partial 4th leaf
324
7
        let code_len = 3 * LEAF_SIZE + partial_leaf_size;
325
7
        let mut code = alloc::vec![0u8; code_len];
326
7
        rng.fill_bytes(code.as_mut_slice());
327
7
328
7
        // Compute root
329
7
        let root = Contract::root_from_code(code.clone());
330
331
        // Compute expected root
332
7
        let expected_root = {
333
7
            let mut tree = BinaryMerkleTree::new();
334
7
335
7
            let leaves = code.chunks(LEAF_SIZE).collect::<Vec<_>>();
336
7
            tree.push(leaves[0]);
337
7
            tree.push(leaves[1]);
338
7
            tree.push(leaves[2]);
339
7
340
7
            // Push partial leaf with manual padding.
341
7
            // We start by generating an n-byte array, where n is the code
342
7
            // length rounded to the nearest multiple of 8, and each byte is the
343
7
            // PADDING_BYTE by default. The leaf is generated by copying the
344
7
            // remaining data bytes into the start of this array.
345
7
            let sz = partial_leaf_size.next_multiple_of(8);
346
7
            if sz > 0 {
  Branch (346:16): [True: 6, False: 1]
347
6
                let mut padded_leaf = vec![PADDING_BYTE; sz];
348
6
                padded_leaf[0..partial_leaf_size].clone_from_slice(leaves[3]);
349
6
                tree.push(&padded_leaf);
350
6
            }
1
351
7
            tree.root().into()
352
7
        };
353
7
354
7
        assert_eq!(root, expected_root);
355
7
    }
356
357
    #[test]
358
1
    fn empty_contract_id() {
359
1
        let contract = Contract::from(vec![]);
360
1
        let salt = Salt::zeroed();
361
1
        let root = contract.root();
362
1
        let state_root = Contract::default_state_root();
363
1
364
1
        let calculated_id = contract.id(&salt, &root, &state_root);
365
1
        assert_eq!(calculated_id, Contract::EMPTY_CONTRACT_ID)
366
1
    }
367
}