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}