1use super::Interpreter;
2use crate::prelude::*;
3use fuel_asm::RegId;
4
5impl<M, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal>
6where
7 Tx: ExecutableTransaction,
8{
9 pub const fn single_stepping(&self) -> bool {
11 self.debugger.single_stepping()
12 }
13
14 pub fn set_single_stepping(&mut self, single_stepping: bool) {
16 self.debugger.set_single_stepping(single_stepping)
17 }
18
19 pub fn clear_breakpoints(&mut self) {
21 self.debugger.clear_breakpoints();
22 }
23
24 pub fn set_breakpoint(&mut self, breakpoint: Breakpoint) {
26 self.debugger.set_breakpoint(breakpoint)
27 }
28
29 pub fn overwrite_breakpoints(&mut self, breakpoints: &[Breakpoint]) {
31 self.debugger.clear_breakpoints();
32 for bp in breakpoints {
33 self.debugger.set_breakpoint(*bp);
34 }
35 }
36
37 pub fn remove_breakpoint(&mut self, breakpoint: &Breakpoint) {
39 self.debugger.remove_breakpoint(breakpoint)
40 }
41
42 pub(crate) fn eval_debugger_state(&mut self) -> DebugEval {
43 let debugger = &mut self.debugger;
44
45 let contract = self.frames.last().map(CallFrame::to);
46 let pc = self.registers[RegId::PC].saturating_sub(self.registers[RegId::IS]);
47
48 debugger.eval_state(contract, pc)
49 }
50
51 pub(crate) fn debugger_set_last_state(&mut self, state: ProgramState) {
52 self.debugger.set_last_state(state)
53 }
54
55 pub(crate) const fn debugger_last_state(&self) -> &Option<ProgramState> {
56 self.debugger.last_state()
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use alloc::{
63 vec,
64 vec::Vec,
65 };
66
67 use super::Interpreter;
68 use crate::prelude::*;
69 use fuel_asm::RegId;
70
71 #[test]
72 fn breakpoint_script() {
73 use fuel_asm::op;
74 use fuel_tx::ConsensusParameters;
75
76 let mut vm = Interpreter::<_, _, _>::with_memory_storage();
77
78 let gas_limit = 1_000_000;
79 let gas_price = 0;
80 let height = Default::default();
81
82 let script = [
83 op::addi(0x10, RegId::ZERO, 8),
84 op::addi(0x11, RegId::ZERO, 16),
85 op::addi(0x12, RegId::ZERO, 32),
86 op::addi(0x13, RegId::ZERO, 64),
87 op::addi(0x14, RegId::ZERO, 128),
88 op::ret(0x10),
89 ]
90 .into_iter()
91 .collect();
92
93 let consensus_params = ConsensusParameters::standard();
94
95 let tx = TransactionBuilder::script(script, vec![])
96 .script_gas_limit(gas_limit)
97 .add_fee_input()
98 .finalize()
99 .into_checked(height, &consensus_params)
100 .expect("failed to generate checked tx")
101 .into_ready(
102 gas_price,
103 consensus_params.gas_costs(),
104 consensus_params.fee_params(),
105 None,
106 )
107 .unwrap();
108
109 let suite = vec![
110 (
111 Breakpoint::script(0),
112 vec![(0x10, 0), (0x11, 0), (0x12, 0), (0x13, 0), (0x14, 0)],
113 ),
114 (
115 Breakpoint::script(2),
116 vec![(0x10, 8), (0x11, 16), (0x12, 0), (0x13, 0), (0x14, 0)],
117 ),
118 (
119 Breakpoint::script(3),
120 vec![(0x10, 8), (0x11, 16), (0x12, 32), (0x13, 0), (0x14, 0)],
121 ),
122 (
123 Breakpoint::script(5),
124 vec![(0x10, 8), (0x11, 16), (0x12, 32), (0x13, 64), (0x14, 128)],
125 ),
126 ];
127
128 suite.iter().for_each(|(b, _)| vm.set_breakpoint(*b));
129
130 let state = vm
131 .transact(tx)
132 .map(ProgramState::from)
133 .expect("Failed to execute script!");
134
135 suite
136 .into_iter()
137 .fold(state, |state, (breakpoint, registers)| {
138 let debug = state.debug_ref().expect("Expected breakpoint");
139 let b = debug
140 .breakpoint()
141 .expect("State without expected breakpoint");
142
143 assert_eq!(&breakpoint, b);
144 registers.into_iter().for_each(|(r, w)| {
145 assert_eq!(w, vm.registers()[r]);
146 });
147
148 vm.resume().expect("Failed to resume")
149 });
150 }
151
152 #[test]
153 fn single_stepping() {
154 use fuel_asm::op;
155 use fuel_tx::ConsensusParameters;
156
157 let mut vm = Interpreter::<_, _, _>::with_memory_storage();
158
159 let gas_limit = 1_000_000;
160 let height = Default::default();
161 let gas_price = 0;
162
163 let script = [
165 op::addi(0x10, RegId::ZERO, 5),
166 op::addi(0x11, 0x11, 1),
167 op::jnei(0x10, 0x11, 1),
168 op::ret(0x10),
169 ]
170 .into_iter()
171 .collect();
172
173 let consensus_params = ConsensusParameters::standard();
174
175 let tx = TransactionBuilder::script(script, vec![])
176 .script_gas_limit(gas_limit)
177 .add_fee_input()
178 .finalize()
179 .into_checked(height, &consensus_params)
180 .expect("failed to generate checked tx")
181 .into_ready(
182 gas_price,
183 consensus_params.gas_costs(),
184 consensus_params.fee_params(),
185 None,
186 )
187 .unwrap();
188
189 vm.set_single_stepping(true);
190
191 let mut state = vm
192 .transact(tx)
193 .map(ProgramState::from)
194 .expect("Failed to execute script!");
195
196 let mut stops = Vec::new();
197
198 while let Some(debug) = state.debug_ref() {
199 let b = debug
200 .breakpoint()
201 .expect("State without expected breakpoint");
202
203 stops.push(b.pc());
204
205 state = vm.resume().expect("Failed to resume");
206 }
207
208 assert_eq!(stops, vec![0, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 12]);
209 }
210
211 #[test]
212 fn resume_without_debug() {
213 use fuel_asm::op;
214 use fuel_tx::ConsensusParameters;
215
216 let mut vm = Interpreter::<_, _, _>::with_memory_storage();
217 vm.resume()
218 .expect_err("Expected error when resuming without debug");
219
220 let gas_limit = 1_000_000;
221 let height = Default::default();
222 let gas_price = 0;
223
224 let script: Vec<u8> = [op::ret(0x10)].into_iter().collect();
226
227 let consensus_params = ConsensusParameters::standard();
228
229 let tx = TransactionBuilder::script(script, vec![])
230 .script_gas_limit(gas_limit)
231 .add_fee_input()
232 .finalize()
233 .into_checked(height, &consensus_params)
234 .expect("failed to generate checked tx")
235 .into_ready(
236 gas_price,
237 consensus_params.gas_costs(),
238 consensus_params.fee_params(),
239 None,
240 )
241 .unwrap();
242
243 let _ = vm
244 .transact(tx)
245 .map(ProgramState::from)
246 .expect("Failed to execute script!");
247
248 vm.resume()
249 .expect_err("Expected error when resuming without debug");
250 }
251}