Coverage Report

Created: 2024-09-27 09:27

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