fuel_vm/interpreter/
metadata.rs

1use super::{
2    ExecutableTransaction,
3    ExecutableTxType,
4    Interpreter,
5    Memory,
6    internal::inc_pc,
7};
8use crate::{
9    call::CallFrame,
10    constraints::reg_key::*,
11    consts::*,
12    context::Context,
13    convert,
14    error::SimpleResult,
15};
16use fuel_asm::{
17    GMArgs,
18    GTFArgs,
19    PanicReason,
20    RegId,
21};
22use fuel_tx::{
23    Input,
24    InputRepr,
25    Output,
26    OutputRepr,
27    UtxoId,
28    field::{
29        BlobId,
30        BytecodeRoot,
31        BytecodeWitnessIndex,
32        ProofSet,
33        Salt,
34        Script as ScriptField,
35        ScriptData,
36        ScriptGasLimit,
37        StorageSlots,
38        SubsectionIndex,
39        SubsectionsNumber,
40        UpgradePurpose,
41    },
42    policies::PolicyType,
43};
44use fuel_types::{
45    ChainId,
46    Immediate12,
47    Immediate18,
48    Word,
49};
50
51#[cfg(test)]
52mod tests;
53
54impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
55where
56    M: Memory,
57    Tx: ExecutableTransaction,
58{
59    pub(crate) fn metadata(&mut self, ra: RegId, imm: Immediate18) -> SimpleResult<()> {
60        let tx_offset = self.tx_offset() as Word;
61        let chain_id = self.chain_id();
62        let gas_price = self.gas_price();
63        let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers);
64        let result = &mut w[WriteRegKey::try_from(ra)?];
65        metadata(
66            &self.context,
67            &self.frames,
68            pc,
69            result,
70            imm,
71            chain_id,
72            tx_offset,
73            gas_price,
74            self.owner_ptr,
75        )
76    }
77
78    pub(crate) fn get_transaction_field(
79        &mut self,
80        ra: RegId,
81        b: Word,
82        imm: Immediate12,
83    ) -> SimpleResult<()> {
84        let tx_offset = self.tx_offset();
85        // Tx size is stored just below the tx bytes
86        let tx_size_ptr = tx_offset.checked_sub(8).expect("Tx offset is not valid");
87        let tx_size = Word::from_be_bytes(
88            self.memory()
89                .read_bytes(tx_size_ptr)
90                .expect("Tx length not in memory"),
91        );
92        let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers);
93        let result = &mut w[WriteRegKey::try_from(ra)?];
94        let input = GTFInput {
95            tx: &self.tx,
96            input_contracts_index_to_output_index: &self
97                .input_contracts_index_to_output_index,
98            tx_offset,
99            tx_size,
100            pc,
101        };
102        input.get_transaction_field(result, b, imm)
103    }
104}
105
106#[allow(clippy::too_many_arguments)]
107pub(crate) fn metadata(
108    context: &Context,
109    frames: &[CallFrame],
110    pc: RegMut<PC>,
111    result: &mut Word,
112    imm: Immediate18,
113    chain_id: ChainId,
114    tx_offset: Word,
115    gas_price: Word,
116    owner_ptr: Option<Word>,
117) -> SimpleResult<()> {
118    let parent = context
119        .is_internal()
120        .then(|| frames.last().map(|f| f.registers()[RegId::FP]))
121        .flatten();
122
123    *result = match GMArgs::try_from(imm)? {
124        GMArgs::GetVerifyingPredicate => context
125            .predicate()
126            .map(|p| p.idx() as Word)
127            .ok_or(PanicReason::TransactionValidity)?,
128        GMArgs::GetChainId => chain_id.into(),
129        GMArgs::BaseAssetId => VM_MEMORY_BASE_ASSET_ID_OFFSET as Word,
130        GMArgs::TxStart => tx_offset,
131        GMArgs::GetCaller => match parent {
132            Some(0) => return Err(PanicReason::ExpectedNestedCaller.into()),
133            Some(parent) => parent,
134            None => return Err(PanicReason::ExpectedInternalContext.into()),
135        },
136        GMArgs::IsCallerExternal => match parent {
137            Some(p) => (p == 0) as Word,
138            None => return Err(PanicReason::ExpectedInternalContext.into()),
139        },
140        GMArgs::GetGasPrice => match context {
141            Context::PredicateVerification { .. }
142            | Context::PredicateEstimation { .. } => {
143                return Err(PanicReason::CanNotGetGasPriceInPredicate.into())
144            }
145            _ => gas_price,
146        },
147        GMArgs::GetOwner => match owner_ptr {
148            None => return Err(PanicReason::OwnerIsUnknown.into()),
149            Some(ptr) => ptr,
150        },
151    };
152
153    inc_pc(pc)?;
154    Ok(())
155}
156
157struct GTFInput<'vm, Tx> {
158    tx: &'vm Tx,
159    input_contracts_index_to_output_index: &'vm alloc::collections::BTreeMap<u16, u16>,
160    tx_offset: usize,
161    tx_size: Word,
162    pc: RegMut<'vm, PC>,
163}
164
165impl<Tx> GTFInput<'_, Tx> {
166    #[allow(deprecated)]
167    pub(crate) fn get_transaction_field(
168        self,
169        result: &mut Word,
170        b: Word,
171        imm: Immediate12,
172    ) -> SimpleResult<()>
173    where
174        Tx: ExecutableTransaction,
175    {
176        let b = convert::to_usize(b).ok_or(PanicReason::InvalidMetadataIdentifier)?;
177        let args = GTFArgs::try_from(imm)?;
178        let tx = self.tx;
179        let input_contract_to_output_index = self.input_contracts_index_to_output_index;
180        let ofs = self.tx_offset;
181
182        // We use saturating_add with tx offset below.
183        // In case any addition overflows, this function returns value
184        // for the field that's above VM_MAX_RAM.
185
186        let a = match args {
187            GTFArgs::Type => Tx::transaction_type() as Word,
188
189            // General
190            GTFArgs::ScriptGasLimit => tx
191                .as_script()
192                .map(|script| *script.script_gas_limit())
193                .unwrap_or_default(),
194            GTFArgs::PolicyTypes => tx.policies().bits() as Word,
195            GTFArgs::PolicyTip => tx
196                .policies()
197                .get(PolicyType::Tip)
198                .ok_or(PanicReason::PolicyIsNotSet)?,
199            GTFArgs::PolicyWitnessLimit => tx
200                .policies()
201                .get(PolicyType::WitnessLimit)
202                .ok_or(PanicReason::PolicyIsNotSet)?,
203            GTFArgs::PolicyMaturity => tx
204                .policies()
205                .get(PolicyType::Maturity)
206                .ok_or(PanicReason::PolicyIsNotSet)?,
207            GTFArgs::PolicyExpiration => tx
208                .policies()
209                .get(PolicyType::Expiration)
210                .ok_or(PanicReason::PolicyIsNotSet)?,
211            GTFArgs::PolicyMaxFee => tx
212                .policies()
213                .get(PolicyType::MaxFee)
214                .ok_or(PanicReason::PolicyIsNotSet)?,
215            GTFArgs::PolicyOwner => tx
216                .policies()
217                .get(PolicyType::Owner)
218                .ok_or(PanicReason::PolicyIsNotSet)?,
219            GTFArgs::ScriptInputsCount
220            | GTFArgs::CreateInputsCount
221            | GTFArgs::TxInputsCount => tx.inputs().len() as Word,
222            GTFArgs::ScriptOutputsCount
223            | GTFArgs::CreateOutputsCount
224            | GTFArgs::TxOutputsCount => tx.outputs().len() as Word,
225            GTFArgs::ScriptWitnessesCount
226            | GTFArgs::CreateWitnessesCount
227            | GTFArgs::TxWitnessesCount => tx.witnesses().len() as Word,
228            GTFArgs::ScriptInputAtIndex
229            | GTFArgs::CreateInputAtIndex
230            | GTFArgs::TxInputAtIndex => ofs
231                .saturating_add(tx.inputs_offset_at(b).ok_or(PanicReason::InputNotFound)?)
232                as Word,
233            GTFArgs::ScriptOutputAtIndex
234            | GTFArgs::CreateOutputAtIndex
235            | GTFArgs::TxOutputAtIndex => ofs.saturating_add(
236                tx.outputs_offset_at(b).ok_or(PanicReason::OutputNotFound)?,
237            ) as Word,
238            GTFArgs::ScriptWitnessAtIndex
239            | GTFArgs::CreateWitnessAtIndex
240            | GTFArgs::TxWitnessAtIndex => ofs.saturating_add(
241                tx.witnesses_offset_at(b)
242                    .ok_or(PanicReason::WitnessNotFound)?,
243            ) as Word,
244            GTFArgs::TxLength => self.tx_size,
245
246            // Input
247            GTFArgs::InputType => {
248                tx.inputs()
249                    .get(b)
250                    .map(InputRepr::from)
251                    .ok_or(PanicReason::InputNotFound)? as Word
252            }
253            GTFArgs::InputCoinTxId => ofs.saturating_add(
254                tx.inputs()
255                    .get(b)
256                    .filter(|i| i.is_coin())
257                    .map(Input::repr)
258                    .and_then(|r| r.utxo_id_offset())
259                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
260                    .ok_or(PanicReason::InputNotFound)?,
261            ) as Word,
262            GTFArgs::InputCoinOutputIndex => {
263                tx.inputs()
264                    .get(b)
265                    .filter(|i| i.is_coin())
266                    .and_then(Input::utxo_id)
267                    .map(UtxoId::output_index)
268                    .ok_or(PanicReason::InputNotFound)? as Word
269            }
270            GTFArgs::InputCoinOwner => ofs.saturating_add(
271                tx.inputs()
272                    .get(b)
273                    .filter(|i| i.is_coin())
274                    .map(Input::repr)
275                    .and_then(|r| r.owner_offset())
276                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
277                    .ok_or(PanicReason::InputNotFound)?,
278            ) as Word,
279            GTFArgs::InputCoinAmount => tx
280                .inputs()
281                .get(b)
282                .filter(|i| i.is_coin())
283                .and_then(Input::amount)
284                .ok_or(PanicReason::InputNotFound)?,
285            GTFArgs::InputCoinAssetId => ofs.saturating_add(
286                tx.inputs()
287                    .get(b)
288                    .filter(|i| i.is_coin())
289                    .map(Input::repr)
290                    .and_then(|r| r.asset_id_offset())
291                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
292                    .ok_or(PanicReason::InputNotFound)?,
293            ) as Word,
294            GTFArgs::InputCoinTxPointer => ofs.saturating_add(
295                tx.inputs()
296                    .get(b)
297                    .filter(|i| i.is_coin())
298                    .map(Input::repr)
299                    .and_then(|r| r.tx_pointer_offset())
300                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
301                    .ok_or(PanicReason::InputNotFound)?,
302            ) as Word,
303            GTFArgs::InputCoinWitnessIndex => {
304                tx.inputs()
305                    .get(b)
306                    .filter(|i| i.is_coin())
307                    .and_then(Input::witness_index)
308                    .ok_or(PanicReason::InputNotFound)? as Word
309            }
310            GTFArgs::InputCoinPredicateLength => {
311                tx.inputs()
312                    .get(b)
313                    .filter(|i| i.is_coin())
314                    .and_then(Input::predicate_len)
315                    .ok_or(PanicReason::InputNotFound)? as Word
316            }
317            GTFArgs::InputCoinPredicateDataLength => {
318                tx.inputs()
319                    .get(b)
320                    .filter(|i| i.is_coin())
321                    .and_then(Input::predicate_data_len)
322                    .ok_or(PanicReason::InputNotFound)? as Word
323            }
324            GTFArgs::InputCoinPredicateGasUsed => {
325                tx.inputs()
326                    .get(b)
327                    .filter(|i| i.is_coin())
328                    .and_then(Input::predicate_gas_used)
329                    .ok_or(PanicReason::InputNotFound)? as Word
330            }
331            GTFArgs::InputCoinPredicate => ofs.saturating_add(
332                tx.inputs()
333                    .get(b)
334                    .filter(|i| i.is_coin())
335                    .and_then(Input::predicate_offset)
336                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
337                    .ok_or(PanicReason::InputNotFound)?,
338            ) as Word,
339            GTFArgs::InputCoinPredicateData => ofs.saturating_add(
340                tx.inputs()
341                    .get(b)
342                    .filter(|i| i.is_coin())
343                    .and_then(Input::predicate_data_offset)
344                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
345                    .ok_or(PanicReason::InputNotFound)?,
346            ) as Word,
347            GTFArgs::InputContractTxId => ofs.saturating_add(
348                tx.inputs()
349                    .get(b)
350                    .filter(|i| i.is_contract())
351                    .map(Input::repr)
352                    .and_then(|r| r.utxo_id_offset())
353                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
354                    .ok_or(PanicReason::InputNotFound)?,
355            ) as Word,
356            GTFArgs::InputContractOutputIndex => {
357                let b = u16::try_from(b)
358                    .map_err(|_| PanicReason::InvalidMetadataIdentifier)?;
359                input_contract_to_output_index
360                    .get(&b)
361                    .copied()
362                    .ok_or(PanicReason::InputNotFound)? as Word
363            }
364            GTFArgs::InputContractId => ofs.saturating_add(
365                tx.inputs()
366                    .get(b)
367                    .filter(|i| i.is_contract())
368                    .map(Input::repr)
369                    .and_then(|r| r.contract_id_offset())
370                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
371                    .ok_or(PanicReason::InputNotFound)?,
372            ) as Word,
373            GTFArgs::InputMessageSender => ofs.saturating_add(
374                tx.inputs()
375                    .get(b)
376                    .filter(|i| i.is_message())
377                    .map(Input::repr)
378                    .and_then(|r| r.message_sender_offset())
379                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
380                    .ok_or(PanicReason::InputNotFound)?,
381            ) as Word,
382            GTFArgs::InputMessageRecipient => ofs.saturating_add(
383                tx.inputs()
384                    .get(b)
385                    .filter(|i| i.is_message())
386                    .map(Input::repr)
387                    .and_then(|r| r.message_recipient_offset())
388                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
389                    .ok_or(PanicReason::InputNotFound)?,
390            ) as Word,
391            GTFArgs::InputMessageAmount => tx
392                .inputs()
393                .get(b)
394                .filter(|i| i.is_message())
395                .and_then(Input::amount)
396                .ok_or(PanicReason::InputNotFound)?,
397            GTFArgs::InputMessageNonce => ofs.saturating_add(
398                tx.inputs()
399                    .get(b)
400                    .filter(|i| i.is_message())
401                    .map(Input::repr)
402                    .and_then(|r| r.message_nonce_offset())
403                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
404                    .ok_or(PanicReason::InputNotFound)?,
405            ) as Word,
406            GTFArgs::InputMessageWitnessIndex => {
407                tx.inputs()
408                    .get(b)
409                    .filter(|i| i.is_message())
410                    .and_then(Input::witness_index)
411                    .ok_or(PanicReason::InputNotFound)? as Word
412            }
413            GTFArgs::InputMessageDataLength => {
414                tx.inputs()
415                    .get(b)
416                    .filter(|i| i.is_message())
417                    .and_then(Input::input_data_len)
418                    .ok_or(PanicReason::InputNotFound)? as Word
419            }
420            GTFArgs::InputMessagePredicateLength => {
421                tx.inputs()
422                    .get(b)
423                    .filter(|i| i.is_message())
424                    .and_then(Input::predicate_len)
425                    .ok_or(PanicReason::InputNotFound)? as Word
426            }
427            GTFArgs::InputMessagePredicateDataLength => {
428                tx.inputs()
429                    .get(b)
430                    .filter(|i| i.is_message())
431                    .and_then(Input::predicate_data_len)
432                    .ok_or(PanicReason::InputNotFound)? as Word
433            }
434            GTFArgs::InputMessagePredicateGasUsed => {
435                tx.inputs()
436                    .get(b)
437                    .filter(|i| i.is_message())
438                    .and_then(Input::predicate_gas_used)
439                    .ok_or(PanicReason::InputNotFound)? as Word
440            }
441            GTFArgs::InputMessageData => ofs.saturating_add(
442                tx.inputs()
443                    .get(b)
444                    .filter(|i| i.is_message())
445                    .map(Input::repr)
446                    .and_then(|r| r.data_offset())
447                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
448                    .ok_or(PanicReason::InputNotFound)?,
449            ) as Word,
450            GTFArgs::InputMessagePredicate => ofs.saturating_add(
451                tx.inputs()
452                    .get(b)
453                    .filter(|i| i.is_message())
454                    .and_then(Input::predicate_offset)
455                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
456                    .ok_or(PanicReason::InputNotFound)?,
457            ) as Word,
458            GTFArgs::InputMessagePredicateData => ofs.saturating_add(
459                tx.inputs()
460                    .get(b)
461                    .filter(|i| i.is_message())
462                    .and_then(Input::predicate_data_offset)
463                    .and_then(|ofs| tx.inputs_offset_at(b).map(|o| o.saturating_add(ofs)))
464                    .ok_or(PanicReason::InputNotFound)?,
465            ) as Word,
466
467            // Output
468            GTFArgs::OutputType => {
469                tx.outputs()
470                    .get(b)
471                    .map(OutputRepr::from)
472                    .ok_or(PanicReason::OutputNotFound)? as Word
473            }
474            GTFArgs::OutputCoinTo => ofs.saturating_add(
475                tx.outputs()
476                    .get(b)
477                    .filter(|o| o.is_coin() || o.is_change())
478                    .map(Output::repr)
479                    .and_then(|r| r.to_offset())
480                    .and_then(|ofs| {
481                        tx.outputs_offset_at(b).map(|o| o.saturating_add(ofs))
482                    })
483                    .ok_or(PanicReason::OutputNotFound)?,
484            ) as Word,
485            GTFArgs::OutputCoinAmount => tx
486                .outputs()
487                .get(b)
488                .filter(|o| o.is_coin())
489                .and_then(Output::amount)
490                .ok_or(PanicReason::OutputNotFound)?,
491            GTFArgs::OutputCoinAssetId => ofs.saturating_add(
492                tx.outputs()
493                    .get(b)
494                    .filter(|o| o.is_coin() || o.is_change())
495                    .map(Output::repr)
496                    .and_then(|r| r.asset_id_offset())
497                    .and_then(|ofs| {
498                        tx.outputs_offset_at(b).map(|o| o.saturating_add(ofs))
499                    })
500                    .ok_or(PanicReason::OutputNotFound)?,
501            ) as Word,
502            GTFArgs::OutputContractInputIndex => {
503                tx.outputs()
504                    .get(b)
505                    .filter(|o| o.is_contract())
506                    .and_then(Output::input_index)
507                    .ok_or(PanicReason::InputNotFound)? as Word
508            }
509            GTFArgs::OutputContractCreatedContractId => ofs.saturating_add(
510                tx.outputs()
511                    .get(b)
512                    .filter(|o| o.is_contract_created())
513                    .map(Output::repr)
514                    .and_then(|r| r.contract_id_offset())
515                    .and_then(|ofs| {
516                        tx.outputs_offset_at(b).map(|o| o.saturating_add(ofs))
517                    })
518                    .ok_or(PanicReason::OutputNotFound)?,
519            ) as Word,
520            GTFArgs::OutputContractCreatedStateRoot => ofs.saturating_add(
521                tx.outputs()
522                    .get(b)
523                    .filter(|o| o.is_contract_created())
524                    .map(Output::repr)
525                    .and_then(|r| r.contract_created_state_root_offset())
526                    .and_then(|ofs| {
527                        tx.outputs_offset_at(b).map(|o| o.saturating_add(ofs))
528                    })
529                    .ok_or(PanicReason::OutputNotFound)?,
530            ) as Word,
531
532            // Witness
533            GTFArgs::WitnessDataLength => {
534                tx.witnesses()
535                    .get(b)
536                    .map(|w| w.as_ref().len())
537                    .ok_or(PanicReason::WitnessNotFound)? as Word
538            }
539            GTFArgs::WitnessData => {
540                tx.witnesses_offset_at(b)
541                    .map(|w| ofs.saturating_add(w).saturating_add(WORD_SIZE))
542                    .ok_or(PanicReason::WitnessNotFound)? as Word
543            }
544
545            // If it is not any above commands, it is something specific to the
546            // transaction type.
547            specific_args => {
548                match (tx.executable_type(), specific_args) {
549                    // Script
550                    (ExecutableTxType::Script(script), GTFArgs::ScriptLength) => {
551                        script.script().len() as Word
552                    }
553                    (ExecutableTxType::Script(script), GTFArgs::ScriptDataLength) => {
554                        script.script_data().len() as Word
555                    }
556                    (ExecutableTxType::Script(script), GTFArgs::Script) => {
557                        ofs.saturating_add(script.script_offset()) as Word
558                    }
559                    (ExecutableTxType::Script(script), GTFArgs::ScriptData) => {
560                        ofs.saturating_add(script.script_data_offset()) as Word
561                    }
562
563                    // Create
564                    (
565                        ExecutableTxType::Create(create),
566                        GTFArgs::CreateBytecodeWitnessIndex,
567                    ) => *create.bytecode_witness_index() as Word,
568                    (
569                        ExecutableTxType::Create(create),
570                        GTFArgs::CreateStorageSlotsCount,
571                    ) => create.storage_slots().len() as Word,
572                    (ExecutableTxType::Create(create), GTFArgs::CreateSalt) => {
573                        ofs.saturating_add(create.salt_offset()) as Word
574                    }
575                    (
576                        ExecutableTxType::Create(create),
577                        GTFArgs::CreateStorageSlotAtIndex,
578                    ) => {
579                        (ofs.saturating_add(
580                            create
581                                .storage_slots_offset_at(b)
582                                .ok_or(PanicReason::StorageSlotsNotFound)?,
583                        )) as Word
584                    }
585
586                    // Blob
587                    (ExecutableTxType::Blob(blob), GTFArgs::BlobId) => {
588                        ofs.saturating_add(blob.blob_id_offset()) as Word
589                    }
590                    (ExecutableTxType::Blob(blob), GTFArgs::BlobWitnessIndex) => {
591                        *blob.bytecode_witness_index() as Word
592                    }
593
594                    // Upload
595                    (ExecutableTxType::Upload(upload), GTFArgs::UploadRoot) => {
596                        ofs.saturating_add(upload.bytecode_root_offset()) as Word
597                    }
598                    (ExecutableTxType::Upload(upload), GTFArgs::UploadWitnessIndex) => {
599                        *upload.bytecode_witness_index() as Word
600                    }
601                    (
602                        ExecutableTxType::Upload(upload),
603                        GTFArgs::UploadSubsectionIndex,
604                    ) => *upload.subsection_index() as Word,
605                    (
606                        ExecutableTxType::Upload(upload),
607                        GTFArgs::UploadSubsectionsCount,
608                    ) => *upload.subsections_number() as Word,
609                    (ExecutableTxType::Upload(upload), GTFArgs::UploadProofSetCount) => {
610                        upload.proof_set().len() as Word
611                    }
612                    (
613                        ExecutableTxType::Upload(upload),
614                        GTFArgs::UploadProofSetAtIndex,
615                    ) => ofs.saturating_add(
616                        upload
617                            .proof_set_offset_at(b)
618                            .ok_or(PanicReason::ProofInUploadNotFound)?,
619                    ) as Word,
620
621                    // Upgrade
622                    (ExecutableTxType::Upgrade(upgrade), GTFArgs::UpgradePurpose) => {
623                        ofs.saturating_add(upgrade.upgrade_purpose_offset()) as Word
624                    }
625
626                    _ => return Err(PanicReason::InvalidMetadataIdentifier.into()),
627                }
628            }
629        };
630
631        *result = a;
632
633        inc_pc(self.pc)?;
634        Ok(())
635    }
636}