fuel-vm 0.26.0

FuelVM interpreter.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
use super::{ExecutableTransaction, Interpreter};
use crate::consts::*;
use crate::error::RuntimeError;

use fuel_asm::{GMArgs, GTFArgs, PanicReason, RegId};
use fuel_tx::field::{
    BytecodeLength, BytecodeWitnessIndex, ReceiptsRoot, Salt, Script as ScriptField, ScriptData, StorageSlots,
};
use fuel_tx::{Input, InputRepr, Output, OutputRepr, UtxoId};
use fuel_types::{Immediate12, Immediate18, RegisterId, Word};

impl<S, Tx> Interpreter<S, Tx>
where
    Tx: ExecutableTransaction,
{
    pub(crate) fn metadata(&mut self, ra: RegisterId, imm: Immediate18) -> Result<(), RuntimeError> {
        Self::is_register_writable(ra)?;

        let external = self.is_external_context();
        let args = GMArgs::try_from(imm)?;

        if external {
            match args {
                GMArgs::GetVerifyingPredicate => {
                    self.registers[ra] = self
                        .context
                        .predicate()
                        .map(|p| p.idx() as Word)
                        .ok_or(PanicReason::TransactionValidity)?;
                }

                _ => return Err(PanicReason::ExpectedInternalContext.into()),
            }
        } else {
            let parent = self
                .frames
                .last()
                .map(|f| f.registers()[RegId::FP])
                .expect("External context will always have a frame");

            match args {
                GMArgs::IsCallerExternal => {
                    self.registers[ra] = (parent == 0) as Word;
                }

                GMArgs::GetCaller if parent != 0 => {
                    self.registers[ra] = parent;
                }

                _ => return Err(PanicReason::ExpectedInternalContext.into()),
            }
        }

        self.inc_pc()
    }

    pub(crate) fn get_transaction_field(
        &mut self,
        ra: RegisterId,
        b: Word,
        imm: Immediate12,
    ) -> Result<(), RuntimeError> {
        Self::is_register_writable(ra)?;

        let b = b as usize;
        let args = GTFArgs::try_from(imm)?;
        let tx = self.transaction();
        let ofs = self.tx_offset();

        let a = match args {
            GTFArgs::Type => Tx::transaction_type(),

            // General
            GTFArgs::ScriptGasPrice | GTFArgs::CreateGasPrice => tx.price(),
            GTFArgs::ScriptGasLimit | GTFArgs::CreateGasLimit => tx.limit(),
            GTFArgs::ScriptMaturity | GTFArgs::CreateMaturity => *tx.maturity(),
            GTFArgs::ScriptInputsCount | GTFArgs::CreateInputsCount => tx.inputs().len() as Word,
            GTFArgs::ScriptOutputsCount | GTFArgs::CreateOutputsCount => tx.outputs().len() as Word,
            GTFArgs::ScriptWitnessesCound | GTFArgs::CreateWitnessesCount => tx.witnesses().len() as Word,
            GTFArgs::ScriptInputAtIndex | GTFArgs::CreateInputAtIndex => {
                (ofs + tx.inputs_offset_at(b).ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::ScriptOutputAtIndex | GTFArgs::CreateOutputAtIndex => {
                (ofs + tx.outputs_offset_at(b).ok_or(PanicReason::OutputNotFound)?) as Word
            }
            GTFArgs::ScriptWitnessAtIndex | GTFArgs::CreateWitnessAtIndex => {
                (ofs + tx.witnesses_offset_at(b).ok_or(PanicReason::WitnessNotFound)?) as Word
            }

            // Input
            GTFArgs::InputType => tx
                .inputs()
                .get(b)
                .map(InputRepr::from)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputCoinTxId => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_coin())
                    .map(Input::repr)
                    .and_then(|r| r.utxo_id_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputCoinOutputIndex => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_coin())
                .and_then(Input::utxo_id)
                .map(UtxoId::output_index)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputCoinOwner => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_coin())
                    .map(Input::repr)
                    .and_then(|r| r.owner_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputCoinAmount => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_coin())
                .and_then(Input::amount)
                .ok_or(PanicReason::InputNotFound)?,
            GTFArgs::InputCoinAssetId => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_coin())
                    .map(Input::repr)
                    .and_then(|r| r.asset_id_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputCoinTxPointer => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_coin())
                    .map(Input::repr)
                    .and_then(|r| r.tx_pointer_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputCoinWitnessIndex => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_coin())
                .and_then(Input::witness_index)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputCoinMaturity => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_coin())
                .and_then(Input::maturity)
                .ok_or(PanicReason::InputNotFound)?,
            GTFArgs::InputCoinPredicateLength => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_coin())
                .and_then(Input::predicate_len)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputCoinPredicateDataLength => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_coin())
                .and_then(Input::predicate_data_len)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputCoinPredicate => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_coin())
                    .and_then(Input::predicate_offset)
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputCoinPredicateData => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_coin())
                    .and_then(Input::predicate_data_offset)
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputContractTxId => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_contract())
                    .map(Input::repr)
                    .and_then(|r| r.utxo_id_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputContractOutputIndex => tx
                .find_output_contract(b)
                .map(|(idx, _o)| idx)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputContractBalanceRoot => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_contract())
                    .map(Input::repr)
                    .and_then(|r| r.contract_balance_root_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputContractStateRoot => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_contract())
                    .map(Input::repr)
                    .and_then(|r| r.contract_state_root_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputContractTxPointer => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_contract())
                    .map(Input::repr)
                    .and_then(|r| r.tx_pointer_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputContractId => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_contract())
                    .map(Input::repr)
                    .and_then(|r| r.contract_id_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputMessageId => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_message())
                    .map(Input::repr)
                    .and_then(|r| r.message_id_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputMessageSender => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_message())
                    .map(Input::repr)
                    .and_then(|r| r.message_sender_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputMessageRecipient => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_message())
                    .map(Input::repr)
                    .and_then(|r| r.message_recipient_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputMessageAmount => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_message())
                .and_then(Input::amount)
                .ok_or(PanicReason::InputNotFound)?,
            GTFArgs::InputMessageNonce => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_message())
                .and_then(Input::nonce)
                .ok_or(PanicReason::InputNotFound)?,
            GTFArgs::InputMessageWitnessIndex => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_message())
                .and_then(Input::witness_index)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputMessageDataLength => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_message())
                .and_then(Input::input_data)
                .map(|d| d.len() as Word)
                .ok_or(PanicReason::InputNotFound)?,
            GTFArgs::InputMessagePredicateLength => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_message())
                .and_then(Input::predicate_len)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputMessagePredicateDataLength => tx
                .inputs()
                .get(b)
                .filter(|i| i.is_message())
                .and_then(Input::predicate_data_len)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::InputMessageData => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_message())
                    .map(Input::repr)
                    .and_then(|r| r.data_offset())
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputMessagePredicate => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_message())
                    .and_then(Input::predicate_offset)
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }
            GTFArgs::InputMessagePredicateData => {
                (ofs + tx
                    .inputs()
                    .get(b)
                    .filter(|i| i.is_message())
                    .and_then(Input::predicate_data_offset)
                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::InputNotFound)?) as Word
            }

            // Output
            GTFArgs::OutputType => tx
                .outputs()
                .get(b)
                .map(OutputRepr::from)
                .ok_or(PanicReason::OutputNotFound)? as Word,
            GTFArgs::OutputCoinTo => {
                (ofs + tx
                    .outputs()
                    .get(b)
                    .filter(|o| o.is_coin())
                    .map(Output::repr)
                    .and_then(|r| r.to_offset())
                    .and_then(|ofs| tx.outputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::OutputNotFound)?) as Word
            }
            GTFArgs::OutputCoinAmount => tx
                .outputs()
                .get(b)
                .filter(|o| o.is_coin())
                .and_then(Output::amount)
                .ok_or(PanicReason::OutputNotFound)?,
            GTFArgs::OutputCoinAssetId => {
                (ofs + tx
                    .outputs()
                    .get(b)
                    .filter(|o| o.is_coin())
                    .map(Output::repr)
                    .and_then(|r| r.asset_id_offset())
                    .and_then(|ofs| tx.outputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::OutputNotFound)?) as Word
            }
            GTFArgs::OutputContractInputIndex => tx
                .outputs()
                .get(b)
                .filter(|o| o.is_contract())
                .and_then(Output::input_index)
                .ok_or(PanicReason::InputNotFound)? as Word,
            GTFArgs::OutputContractBalanceRoot => {
                (ofs + tx
                    .outputs()
                    .get(b)
                    .filter(|o| o.is_contract())
                    .map(Output::repr)
                    .and_then(|r| r.contract_balance_root_offset())
                    .and_then(|ofs| tx.outputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::OutputNotFound)?) as Word
            }
            GTFArgs::OutputContractStateRoot => {
                (ofs + tx
                    .outputs()
                    .get(b)
                    .filter(|o| o.is_contract())
                    .map(Output::repr)
                    .and_then(|r| r.contract_state_root_offset())
                    .and_then(|ofs| tx.outputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::OutputNotFound)?) as Word
            }
            GTFArgs::OutputMessageRecipient => {
                (ofs + tx
                    .outputs()
                    .get(b)
                    .filter(|o| o.is_message())
                    .map(Output::repr)
                    .and_then(|r| r.recipient_offset())
                    .and_then(|ofs| tx.outputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::OutputNotFound)?) as Word
            }
            GTFArgs::OutputMessageAmount => tx
                .outputs()
                .get(b)
                .filter(|o| o.is_message())
                .and_then(Output::amount)
                .ok_or(PanicReason::OutputNotFound)?,
            GTFArgs::OutputContractCreatedContractId => {
                (ofs + tx
                    .outputs()
                    .get(b)
                    .filter(|o| o.is_contract_created())
                    .map(Output::repr)
                    .and_then(|r| r.contract_id_offset())
                    .and_then(|ofs| tx.outputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::OutputNotFound)?) as Word
            }
            GTFArgs::OutputContractCreatedStateRoot => {
                (ofs + tx
                    .outputs()
                    .get(b)
                    .filter(|o| o.is_contract_created())
                    .map(Output::repr)
                    .and_then(|r| r.contract_created_state_root_offset())
                    .and_then(|ofs| tx.outputs_offset_at(b).map(|o| o + ofs))
                    .ok_or(PanicReason::OutputNotFound)?) as Word
            }

            // Witness
            GTFArgs::WitnessDataLength => tx
                .witnesses()
                .get(b)
                .map(|w| w.as_ref().len())
                .ok_or(PanicReason::WitnessNotFound)? as Word,
            GTFArgs::WitnessData => tx
                .witnesses_offset_at(b)
                .map(|w| ofs + w + WORD_SIZE)
                .ok_or(PanicReason::WitnessNotFound)? as Word,

            // If it is not any above commands, it is something specific to the transaction type.
            specific_args => {
                let as_script = tx.as_script();
                let as_create = tx.as_create();
                match (as_script, as_create, specific_args) {
                    // Script
                    (Some(script), None, GTFArgs::ScriptLength) => script.script().len() as Word,
                    (Some(script), None, GTFArgs::ScriptDataLength) => script.script_data().len() as Word,
                    (Some(script), None, GTFArgs::ScriptReceiptsRoot) => (ofs + script.receipts_root_offset()) as Word,
                    (Some(script), None, GTFArgs::Script) => (ofs + script.script_offset()) as Word,
                    (Some(script), None, GTFArgs::ScriptData) => (ofs + script.script_data_offset()) as Word,

                    // Create
                    (None, Some(create), GTFArgs::CreateBytecodeLength) => *create.bytecode_length() as Word,
                    (None, Some(create), GTFArgs::CreateBytecodeWitnessIndex) => {
                        *create.bytecode_witness_index() as Word
                    }
                    (None, Some(create), GTFArgs::CreateStorageSlotsCount) => create.storage_slots().len() as Word,
                    (None, Some(create), GTFArgs::CreateSalt) => (ofs + create.salt_offset()) as Word,
                    (None, Some(create), GTFArgs::CreateStorageSlotAtIndex) => {
                        // TODO: Maybe we need to return panic error `StorageSlotsNotFound`?
                        (ofs + create.storage_slots_offset_at(b).unwrap_or_default()) as Word
                    }
                    _ => return Err(PanicReason::InvalidMetadataIdentifier.into()),
                }
            }
        };

        self.registers[ra] = a;

        self.inc_pc()
    }
}