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