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}