fuel_vm/
predicate.rs

1//! Predicate representations with required data to be executed during VM runtime
2
3use fuel_tx::field;
4
5use crate::interpreter::MemoryRange;
6
7/// Runtime representation of a predicate
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct RuntimePredicate {
11    range: MemoryRange,
12    idx: usize,
13}
14
15impl RuntimePredicate {
16    /// Empty predicate for testing
17    #[cfg(test)]
18    pub const fn empty() -> Self {
19        Self {
20            range: MemoryRange::new(0, 0),
21            idx: 0,
22        }
23    }
24
25    /// Memory slice with the program representation of the predicate
26    pub const fn program(&self) -> &MemoryRange {
27        &self.range
28    }
29
30    /// Index of the transaction input that maps to this predicate
31    pub const fn idx(&self) -> usize {
32        self.idx
33    }
34
35    /// Create a new runtime predicate from a transaction, given the input index
36    ///
37    /// Return `None` if the tx input doesn't map to an input with a predicate
38    pub fn from_tx<T>(tx: &T, tx_offset: usize, idx: usize) -> Option<Self>
39    where
40        T: field::Inputs,
41    {
42        let (ofs, len) = tx.inputs_predicate_offset_at(idx)?;
43        let addr = ofs.saturating_add(tx_offset);
44        Some(Self {
45            range: MemoryRange::new(addr, len),
46            idx,
47        })
48    }
49}
50
51#[allow(clippy::cast_possible_truncation)]
52#[cfg(test)]
53mod tests {
54    use alloc::{
55        vec,
56        vec::Vec,
57    };
58    use core::iter;
59    use fuel_asm::op;
60    use fuel_tx::{
61        TransactionBuilder,
62        field::ScriptGasLimit,
63    };
64    use fuel_types::bytes;
65    use rand::{
66        Rng,
67        SeedableRng,
68        rngs::StdRng,
69    };
70
71    use crate::{
72        checked_transaction::{
73            CheckPredicateParams,
74            EstimatePredicates,
75        },
76        constraints::reg_key::{
77            HP,
78            IS,
79            ONE,
80            SSP,
81            ZERO,
82        },
83        error::PredicateVerificationFailed,
84        interpreter::{
85            InterpreterParams,
86            NotSupportedEcal,
87        },
88        prelude::{
89            predicates::check_predicates,
90            *,
91        },
92        storage::{
93            BlobData,
94            predicate::empty_predicate_storage,
95        },
96    };
97
98    #[test]
99    fn from_tx_works() {
100        let rng = &mut StdRng::seed_from_u64(2322u64);
101
102        let height = 1.into();
103
104        #[rustfmt::skip]
105        let predicate: Vec<u8> = vec![
106            op::addi(0x10, 0x00, 0x01),
107            op::addi(0x10, 0x10, 0x01),
108            op::ret(0x01),
109        ].into_iter().collect();
110
111        let predicate_data = b"If people do not believe that mathematics is simple, it is only because they do not realize how complicated life is.".to_vec();
112
113        let owner = (*Contract::root_from_code(&predicate)).into();
114        let a = Input::coin_predicate(
115            rng.r#gen(),
116            owner,
117            rng.r#gen(),
118            rng.r#gen(),
119            rng.r#gen(),
120            0,
121            predicate.clone(),
122            predicate_data.clone(),
123        );
124
125        let b = Input::message_coin_predicate(
126            rng.r#gen(),
127            rng.r#gen(),
128            rng.r#gen(),
129            rng.r#gen(),
130            0,
131            predicate.clone(),
132            predicate_data.clone(),
133        );
134
135        let c = Input::message_data_predicate(
136            rng.r#gen(),
137            rng.r#gen(),
138            rng.r#gen(),
139            rng.r#gen(),
140            0,
141            vec![0xff; 10],
142            predicate.clone(),
143            predicate_data,
144        );
145
146        let inputs = vec![a, b, c];
147
148        for i in inputs {
149            let tx = TransactionBuilder::script(vec![], vec![])
150                .add_input(i)
151                .add_fee_input()
152                .finalize_checked_basic(height);
153
154            // assert invalid idx wont panic
155            let idx = 1;
156            let tx_offset = TxParameters::DEFAULT.tx_offset();
157            let runtime = RuntimePredicate::from_tx(tx.as_ref(), tx_offset, idx);
158
159            assert!(runtime.is_none());
160
161            // fetch the input predicate
162            let idx = 0;
163            let runtime = RuntimePredicate::from_tx(tx.as_ref(), tx_offset, idx)
164                .expect("failed to generate predicate from valid tx");
165
166            assert_eq!(idx, runtime.idx());
167
168            let mut interpreter = Interpreter::<_, _, _>::with_storage(
169                MemoryInstance::new(),
170                empty_predicate_storage(),
171                InterpreterParams::default(),
172            );
173
174            assert!(
175                interpreter
176                    .init_predicate(
177                        Context::PredicateVerification {
178                            program: RuntimePredicate::empty(),
179                        },
180                        tx.transaction().clone(),
181                        *tx.transaction().script_gas_limit(),
182                    )
183                    .is_ok()
184            );
185
186            let pad = bytes::padded_len(&predicate).unwrap() - predicate.len();
187
188            // assert we are testing an edge case
189            assert_ne!(0, pad);
190
191            let padded_predicate: Vec<u8> = predicate
192                .iter()
193                .copied()
194                .chain(iter::repeat_n(0u8, pad))
195                .collect();
196
197            let program = runtime.program();
198            let program = &interpreter.memory()[program.usizes()];
199
200            // assert the program in the vm memory is the same of the input
201            assert_eq!(program, &padded_predicate);
202        }
203    }
204
205    fn assert_inputs_are_validated_for_predicates(
206        inputs: Vec<(
207            Vec<Instruction>,
208            bool,
209            Result<(), PredicateVerificationFailed>,
210        )>,
211        blob: Vec<Instruction>,
212    ) {
213        let rng = &mut StdRng::seed_from_u64(2322u64);
214
215        let height = 1.into();
216        let predicate_data =
217            b"If you think it's simple, then you have misunderstood the problem."
218                .to_vec();
219
220        let mut storage = MemoryStorage::new(Default::default(), Default::default());
221
222        let blob_id = BlobId::zeroed();
223        let blob: Vec<u8> = blob.into_iter().collect();
224        storage
225            .storage_as_mut::<BlobData>()
226            .insert(&blob_id, &blob)
227            .unwrap();
228
229        macro_rules! predicate_input {
230            ($predicate:expr) => {{
231                let predicate: Vec<u8> = $predicate.into_iter().collect();
232                let owner = Input::predicate_owner(&predicate);
233                [
234                    Input::coin_predicate(
235                        rng.r#gen(),
236                        owner,
237                        rng.r#gen(),
238                        rng.r#gen(),
239                        rng.r#gen(),
240                        0,
241                        predicate.clone(),
242                        predicate_data.clone(),
243                    ),
244                    Input::message_coin_predicate(
245                        rng.r#gen(),
246                        owner,
247                        rng.r#gen(),
248                        rng.r#gen(),
249                        0,
250                        predicate.clone(),
251                        predicate_data.clone(),
252                    ),
253                    Input::message_data_predicate(
254                        rng.r#gen(),
255                        owner,
256                        rng.r#gen(),
257                        rng.r#gen(),
258                        0,
259                        vec![rng.r#gen(); rng.gen_range(1..100)],
260                        predicate.clone(),
261                        predicate_data.clone(),
262                    ),
263                ]
264            }};
265        }
266
267        for (i, (input_predicate, correct_gas, expected)) in
268            inputs.into_iter().enumerate()
269        {
270            let input_group = predicate_input!(input_predicate);
271            for mut input in input_group {
272                if !correct_gas {
273                    input.set_predicate_gas_used(1234);
274                }
275
276                let mut script = TransactionBuilder::script(
277                    [op::ret(0x01)].into_iter().collect(),
278                    vec![],
279                )
280                .add_input(input)
281                .add_fee_input()
282                .finalize();
283
284                if correct_gas {
285                    script
286                        .estimate_predicates(
287                            &CheckPredicateParams::default(),
288                            MemoryInstance::new(),
289                            &storage,
290                        )
291                        .unwrap();
292                }
293
294                let tx = script
295                    .into_checked_basic(height, &Default::default())
296                    .unwrap();
297
298                let result = check_predicates(
299                    &tx,
300                    &CheckPredicateParams::default(),
301                    MemoryInstance::new(),
302                    &storage,
303                    NotSupportedEcal,
304                );
305
306                assert_eq!(result.map(|_| ()), expected, "failed at input {}", i);
307            }
308        }
309    }
310
311    /// Verifies the runtime predicate validation rules outlined in the spec are actually
312    /// validated https://github.com/FuelLabs/fuel-specs/blob/master/src/fuel-vm/index.md#predicate-verification
313    #[test]
314    fn inputs_are_validated_for_good_predicate_inputs() {
315        const CORRECT_GAS: bool = true;
316        let good_blob = vec![op::noop(), op::ret(0x01)];
317
318        let inputs = vec![
319            (
320                // A valid predicate
321                vec![
322                    op::addi(0x10, 0x00, 0x01),
323                    op::addi(0x10, 0x10, 0x01),
324                    op::ret(0x01),
325                ],
326                CORRECT_GAS,
327                Ok(()),
328            ),
329            (
330                // Use `LDC` with mode `1` to load the blob into the predicate.
331                vec![
332                    // Allocate 32 byte on the heap.
333                    op::movi(0x10, 32),
334                    op::aloc(0x10),
335                    // This will be our zeroed blob id
336                    op::move_(0x10, HP),
337                    // Store the size of the blob
338                    op::bsiz(0x11, 0x10),
339                    // Store start of the blob code
340                    op::move_(0x12, SSP),
341                    // Subtract the start of the code from the end of the code
342                    op::sub(0x12, 0x12, IS),
343                    // Divide the code by the instruction size to get the number of
344                    // instructions
345                    op::divi(0x12, 0x12, Instruction::SIZE as u16),
346                    // Load the blob by `0x10` ID with the `0x11` size
347                    op::ldc(0x10, ZERO, 0x11, 1),
348                    // Jump to a new code location
349                    op::jmp(0x12),
350                ],
351                CORRECT_GAS,
352                Ok(()),
353            ),
354            (
355                // Use `LDC` with mode `2` to load the part of the predicate from the
356                // transaction.
357                vec![
358                    // Skip the return opcodes. One of two opcodes is a good opcode that
359                    // returns `0x1`. This opcode is our source for the `LDC`
360                    // opcode. We will copy return good opcode to the end
361                    // of the `ssp` via `LDC`. And jump there to
362                    // return `true` from the predicate.
363                    op::jmpf(ZERO, 2),
364                    // Bad return opcode that we want to skip.
365                    op::ret(0x0),
366                    // Good return opcode that we want to use for the `LDC`.
367                    op::ret(0x1),
368                    // Take the start of the code and move it for 2 opcodes to get the
369                    // desired opcode to copy.
370                    op::move_(0x10, IS),
371                    // We don't need to copy `jmpf` and bad `ret` opcodes via `LDC`.
372                    op::addi(0x10, 0x10, 2 * Instruction::SIZE as u16),
373                    // Store end of the code
374                    op::move_(0x12, SSP),
375                    // Subtract the start of the code from the end of the code
376                    op::sub(0x12, 0x12, IS),
377                    // Divide the code by the instruction size to get the number of
378                    // instructions
379                    op::divi(0x12, 0x12, Instruction::SIZE as u16),
380                    // We want to load only on good `ret` opcode.
381                    op::movi(0x11, Instruction::SIZE as u32),
382                    // Load the code from the memory address `0x10` with the `0x11` size
383                    op::ldc(0x10, ZERO, 0x11, 2),
384                    // Jump to a new code location
385                    op::jmp(0x12),
386                ],
387                CORRECT_GAS,
388                Ok(()),
389            ),
390        ];
391
392        assert_inputs_are_validated_for_predicates(inputs, good_blob)
393    }
394
395    #[test]
396    fn inputs_are_validated_for_bad_predicate_inputs() {
397        const CORRECT_GAS: bool = true;
398        const INCORRECT_GAS: bool = false;
399
400        let bad_blob = vec![op::noop(), op::ret(0x00)];
401
402        let inputs = vec![
403            (
404                // A valid predicate, but gas amount mismatches
405                vec![
406                    op::addi(0x10, 0x00, 0x01),
407                    op::addi(0x10, 0x10, 0x01),
408                    op::ret(0x01),
409                ],
410                INCORRECT_GAS,
411                Err(PredicateVerificationFailed::GasMismatch { index: 0 }),
412            ),
413            (
414                // Returning an invalid value
415                vec![op::ret(0x0)],
416                CORRECT_GAS,
417                Err(PredicateVerificationFailed::Panic {
418                    index: 0,
419                    reason: PanicReason::PredicateReturnedNonOne,
420                }),
421            ),
422            (
423                // Using a contract instruction
424                vec![op::time(0x20, 0x1), op::ret(0x1)],
425                CORRECT_GAS,
426                Err(PredicateVerificationFailed::PanicInstruction {
427                    index: 0,
428                    instruction: PanicInstruction::error(
429                        PanicReason::ContractInstructionNotAllowed,
430                        op::time(0x20, 0x1).into(),
431                    ),
432                }),
433            ),
434            (
435                // Using a contract instruction
436                vec![op::ldc(ONE, ONE, ONE, 0)],
437                CORRECT_GAS,
438                Err(PredicateVerificationFailed::PanicInstruction {
439                    index: 0,
440                    instruction: PanicInstruction::error(
441                        PanicReason::ContractInstructionNotAllowed,
442                        op::ldc(ONE, ONE, ONE, 0).into(),
443                    ),
444                }),
445            ),
446            (
447                // Use `LDC` with mode `1` to load the blob into the predicate.
448                vec![
449                    // Allocate 32 byte on the heap.
450                    op::movi(0x10, 32),
451                    op::aloc(0x10),
452                    // This will be our zeroed blob id
453                    op::move_(0x10, HP),
454                    // Store the size of the blob
455                    op::bsiz(0x11, 0x10),
456                    // Store start of the blob code
457                    op::move_(0x12, SSP),
458                    // Subtract the start of the code from the end of the code
459                    op::sub(0x12, 0x12, IS),
460                    // Divide the code by the instruction size to get the number of
461                    // instructions
462                    op::divi(0x12, 0x12, Instruction::SIZE as u16),
463                    // Load the blob by `0x10` ID with the `0x11` size
464                    op::ldc(0x10, ZERO, 0x11, 1),
465                    // Jump to a new code location
466                    op::jmp(0x12),
467                ],
468                CORRECT_GAS,
469                Err(PredicateVerificationFailed::Panic {
470                    index: 0,
471                    reason: PanicReason::PredicateReturnedNonOne,
472                }),
473            ),
474            (
475                // Use `LDC` with mode `2` to load the part of the predicate from the
476                // transaction.
477                vec![
478                    // Skip the return opcodes. One of two opcodes is a bad opcode that
479                    // returns `0x0`. This opcode is our source for the `LDC`
480                    // opcode. We will copy return bad opcode to the end
481                    // of the `ssp` via `LDC`. And jump there to
482                    // return `false` from the predicate adn fail.
483                    op::jmpf(ZERO, 2),
484                    // Good return opcode that we want to skip.
485                    op::ret(0x1),
486                    // Bad return opcode that we want to use for the `LDC`.
487                    op::ret(0x0),
488                    // Take the start of the code and move it for 2 opcodes to get the
489                    // desired opcode to copy.
490                    op::move_(0x10, IS),
491                    // We don't need to copy `jmpf` and bad `ret` opcodes via `LDC`.
492                    op::addi(0x10, 0x10, 2 * Instruction::SIZE as u16),
493                    // Store end of the code
494                    op::move_(0x12, SSP),
495                    // Subtract the start of the code from the end of the code
496                    op::sub(0x12, 0x12, IS),
497                    // Divide the code by the instruction size to get the number of
498                    // instructions
499                    op::divi(0x12, 0x12, Instruction::SIZE as u16),
500                    // We want to load only on bad `ret` opcode.
501                    op::movi(0x11, Instruction::SIZE as u32),
502                    // Load the code from the memory address `0x10` with the `0x11` size
503                    op::ldc(0x10, ZERO, 0x11, 2),
504                    // Jump to a new code location
505                    op::jmp(0x12),
506                ],
507                CORRECT_GAS,
508                Err(PredicateVerificationFailed::Panic {
509                    index: 0,
510                    reason: PanicReason::PredicateReturnedNonOne,
511                }),
512            ),
513        ];
514
515        assert_inputs_are_validated_for_predicates(inputs, bad_blob)
516    }
517}