fuel_asm/
panic_instruction.rs

1use core::fmt;
2
3use crate::{
4    Instruction,
5    PanicReason,
6    RawInstruction,
7    Word,
8};
9
10#[derive(Clone, Copy, PartialEq, Eq, Hash)]
11#[cfg_attr(feature = "typescript", wasm_bindgen::prelude::wasm_bindgen)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
14/// Describe a panic reason with the instruction that generated it
15pub struct PanicInstruction {
16    #[canonical(skip)]
17    reason: PanicReason,
18    instruction: RawInstruction,
19}
20
21impl PanicInstruction {
22    /// Represents an error described by a reason and an instruction.
23    pub const fn error(reason: PanicReason, instruction: RawInstruction) -> Self {
24        Self {
25            reason,
26            instruction,
27        }
28    }
29
30    /// Underlying panic reason
31    pub const fn reason(&self) -> &PanicReason {
32        &self.reason
33    }
34
35    /// Underlying instruction
36    pub const fn instruction(&self) -> &RawInstruction {
37        &self.instruction
38    }
39}
40
41/// Helper struct to debug-format a `RawInstruction` in `PanicInstruction::fmt`.
42struct InstructionDbg(RawInstruction);
43impl fmt::Debug for InstructionDbg {
44    /// Formats like this: `MOVI { dst: 32, val: 32 } (bytes: 72 80 00 20)`}`
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match Instruction::try_from(self.0) {
47            Ok(instr) => write!(f, "{:?}", instr)?,
48            Err(_) => write!(f, "Unknown")?,
49        };
50        write!(f, " (bytes: ")?;
51        for (i, byte) in self.0.to_be_bytes().iter().enumerate() {
52            if i != 0 {
53                write!(f, " ")?;
54            }
55            write!(f, "{:02x}", byte)?;
56        }
57        write!(f, ")")
58    }
59}
60
61impl fmt::Debug for PanicInstruction {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        f.debug_struct("PanicInstruction")
64            .field("reason", &self.reason)
65            .field("instruction", &InstructionDbg(self.instruction))
66            .finish()
67    }
68}
69
70#[cfg(feature = "typescript")]
71#[wasm_bindgen::prelude::wasm_bindgen]
72impl PanicInstruction {
73    /// Represents an error described by a reason and an instruction.
74    #[wasm_bindgen(constructor)]
75    pub fn error_typescript(reason: PanicReason, instruction: RawInstruction) -> Self {
76        Self::error(reason, instruction)
77    }
78
79    /// Underlying panic reason
80    #[wasm_bindgen(js_name = reason)]
81    pub fn reason_typescript(&self) -> PanicReason {
82        *self.reason()
83    }
84
85    /// Underlying instruction
86    #[wasm_bindgen(js_name = instruction)]
87    pub fn instruction_typescript(&self) -> RawInstruction {
88        *self.instruction()
89    }
90}
91
92const WORD_SIZE: usize = core::mem::size_of::<Word>();
93const REASON_OFFSET: Word = (WORD_SIZE * 8 - 8) as Word;
94const INSTR_OFFSET: Word = REASON_OFFSET - (Instruction::SIZE * 8) as Word;
95
96impl From<PanicInstruction> for Word {
97    fn from(r: PanicInstruction) -> Word {
98        let reason = Word::from(r.reason as u8);
99        let instruction = Word::from(r.instruction);
100        (reason << REASON_OFFSET) | (instruction << INSTR_OFFSET)
101    }
102}
103
104impl From<Word> for PanicInstruction {
105    #[allow(clippy::cast_possible_truncation)]
106    fn from(val: Word) -> Self {
107        // Safe to cast as we've shifted the 8 MSB.
108        let reason_u8 = (val >> REASON_OFFSET) as u8;
109        // Cast to truncate in order to remove the `reason` bits.
110        let instruction = (val >> INSTR_OFFSET) as u32;
111        let reason = PanicReason::from(reason_u8);
112        Self {
113            reason,
114            instruction,
115        }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::op;
123    use fuel_types::canonical::Serialize;
124
125    #[test]
126    fn canonical_serialization_ignores_panic_reason() {
127        let revert_panic_instruction =
128            PanicInstruction::error(PanicReason::Revert, op::noop().into());
129        let out_of_gas_panic_instruction =
130            PanicInstruction::error(PanicReason::OutOfGas, op::noop().into());
131        assert_eq!(
132            revert_panic_instruction.to_bytes(),
133            out_of_gas_panic_instruction.to_bytes()
134        );
135    }
136}