rtvm_interpreter/instructions/
control.rs

1use super::utility::{read_i16, read_u16};
2use crate::{
3    gas,
4    primitives::{Bytes, Spec, U256},
5    Host, InstructionResult, Interpreter, InterpreterResult,
6};
7
8pub fn rjump<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
9    require_eof!(interpreter);
10    gas!(interpreter, gas::BASE);
11    let offset = unsafe { read_i16(interpreter.instruction_pointer) } as isize;
12    // In spec it is +3 but pointer is already incremented in
13    // `Interpreter::step` so for rtvm is +2.
14    interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.offset(offset + 2) };
15}
16
17pub fn rjumpi<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
18    require_eof!(interpreter);
19    gas!(interpreter, gas::CONDITION_JUMP_GAS);
20    pop!(interpreter, condition);
21    // In spec it is +3 but pointer is already incremented in
22    // `Interpreter::step` so for rtvm is +2.
23    let mut offset = 2;
24    if !condition.is_zero() {
25        offset += unsafe { read_i16(interpreter.instruction_pointer) } as isize;
26    }
27
28    interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.offset(offset) };
29}
30
31pub fn rjumpv<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
32    require_eof!(interpreter);
33    gas!(interpreter, gas::CONDITION_JUMP_GAS);
34    pop!(interpreter, case);
35    let case = as_isize_saturated!(case);
36
37    let max_index = unsafe { *interpreter.instruction_pointer } as isize;
38    // for number of items we are adding 1 to max_index, multiply by 2 as each offset is 2 bytes
39    // and add 1 for max_index itself. Note that rtvm already incremented the instruction pointer
40    let mut offset = (max_index + 1) * 2 + 1;
41
42    if case <= max_index {
43        offset += unsafe {
44            read_i16(
45                interpreter
46                    .instruction_pointer
47                    // offset for max_index that is one byte
48                    .offset(1 + case * 2),
49            )
50        } as isize;
51    }
52
53    interpreter.instruction_pointer = unsafe { interpreter.instruction_pointer.offset(offset) };
54}
55
56pub fn jump<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
57    gas!(interpreter, gas::MID);
58    pop!(interpreter, target);
59    jump_inner(interpreter, target);
60}
61
62pub fn jumpi<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
63    gas!(interpreter, gas::HIGH);
64    pop!(interpreter, target, cond);
65    if cond != U256::ZERO {
66        jump_inner(interpreter, target);
67    }
68}
69
70#[inline]
71fn jump_inner(interpreter: &mut Interpreter, target: U256) {
72    let target = as_usize_or_fail!(interpreter, target, InstructionResult::InvalidJump);
73    if !interpreter.contract.is_valid_jump(target) {
74        interpreter.instruction_result = InstructionResult::InvalidJump;
75        return;
76    }
77    // SAFETY: `is_valid_jump` ensures that `dest` is in bounds.
78    interpreter.instruction_pointer = unsafe { interpreter.bytecode.as_ptr().add(target) };
79}
80
81pub fn jumpdest_or_nop<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
82    gas!(interpreter, gas::JUMPDEST);
83}
84
85pub fn callf<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
86    require_eof!(interpreter);
87    gas!(interpreter, gas::LOW);
88
89    let idx = unsafe { read_u16(interpreter.instruction_pointer) } as usize;
90    // TODO Check stack with EOF types.
91
92    if interpreter.function_stack.return_stack_len() == 1024 {
93        interpreter.instruction_result = InstructionResult::EOFFunctionStackOverflow;
94        return;
95    }
96
97    // push current idx and PC to the callf stack.
98    // PC is incremented by 2 to point to the next instruction after callf.
99    interpreter
100        .function_stack
101        .push(interpreter.program_counter() + 2, idx);
102
103    interpreter.load_eof_code(idx, 0)
104}
105
106pub fn retf<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
107    require_eof!(interpreter);
108    gas!(interpreter, gas::RETF_GAS);
109
110    let Some(fframe) = interpreter.function_stack.pop() else {
111        panic!("Expected function frame")
112    };
113
114    interpreter.load_eof_code(fframe.idx, fframe.pc);
115}
116
117pub fn jumpf<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
118    require_eof!(interpreter);
119    gas!(interpreter, gas::LOW);
120
121    let idx = unsafe { read_u16(interpreter.instruction_pointer) } as usize;
122
123    // TODO(EOF) do types stack checks
124
125    interpreter.function_stack.set_current_code_idx(idx);
126    interpreter.load_eof_code(idx, 0)
127}
128
129pub fn pc<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
130    gas!(interpreter, gas::BASE);
131    // - 1 because we have already advanced the instruction pointer in `Interpreter::step`
132    push!(interpreter, U256::from(interpreter.program_counter() - 1));
133}
134
135#[inline]
136fn return_inner(interpreter: &mut Interpreter, instruction_result: InstructionResult) {
137    // zero gas cost
138    // gas!(interpreter, gas::ZERO);
139    pop!(interpreter, offset, len);
140    let len = as_usize_or_fail!(interpreter, len);
141    // important: offset must be ignored if len is zeros
142    let mut output = Bytes::default();
143    if len != 0 {
144        let offset = as_usize_or_fail!(interpreter, offset);
145        resize_memory!(interpreter, offset, len);
146
147        output = interpreter.shared_memory.slice(offset, len).to_vec().into()
148    }
149    interpreter.instruction_result = instruction_result;
150    interpreter.next_action = crate::InterpreterAction::Return {
151        result: InterpreterResult {
152            output,
153            gas: interpreter.gas,
154            result: instruction_result,
155        },
156    };
157}
158
159pub fn ret<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
160    return_inner(interpreter, InstructionResult::Return);
161}
162
163/// EIP-140: REVERT instruction
164pub fn revert<H: Host + ?Sized, SPEC: Spec>(interpreter: &mut Interpreter, _host: &mut H) {
165    check!(interpreter, BYZANTIUM);
166    return_inner(interpreter, InstructionResult::Revert);
167}
168
169/// Stop opcode. This opcode halts the execution.
170pub fn stop<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
171    interpreter.instruction_result = InstructionResult::Stop;
172}
173
174/// Invalid opcode. This opcode halts the execution.
175pub fn invalid<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
176    interpreter.instruction_result = InstructionResult::InvalidFEOpcode;
177}
178
179/// Unknown opcode. This opcode halts the execution.
180pub fn unknown<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
181    interpreter.instruction_result = InstructionResult::OpcodeNotFound;
182}
183
184#[cfg(test)]
185mod test {
186    use rtvm_primitives::{bytes, Bytecode, Eof, PragueSpec};
187
188    use super::*;
189    use crate::{
190        opcode::{make_instruction_table, CALLF, JUMPF, NOP, RETF, RJUMP, RJUMPI, RJUMPV, STOP},
191        DummyHost, FunctionReturnFrame, Gas, Interpreter,
192    };
193
194    #[test]
195    fn rjump() {
196        let table = make_instruction_table::<_, PragueSpec>();
197        let mut host = DummyHost::default();
198        let mut interp = Interpreter::new_bytecode(Bytecode::LegacyRaw(Bytes::from([
199            RJUMP, 0x00, 0x02, STOP, STOP,
200        ])));
201        interp.is_eof = true;
202        interp.gas = Gas::new(10000);
203
204        interp.step(&table, &mut host);
205        assert_eq!(interp.program_counter(), 5);
206    }
207
208    #[test]
209    fn rjumpi() {
210        let table = make_instruction_table::<_, PragueSpec>();
211        let mut host = DummyHost::default();
212        let mut interp = Interpreter::new_bytecode(Bytecode::LegacyRaw(Bytes::from([
213            RJUMPI, 0x00, 0x03, RJUMPI, 0x00, 0x01, STOP, STOP,
214        ])));
215        interp.is_eof = true;
216        interp.stack.push(U256::from(1)).unwrap();
217        interp.stack.push(U256::from(0)).unwrap();
218        interp.gas = Gas::new(10000);
219
220        // dont jump
221        interp.step(&table, &mut host);
222        assert_eq!(interp.program_counter(), 3);
223        // jumps to last opcode
224        interp.step(&table, &mut host);
225        assert_eq!(interp.program_counter(), 7);
226    }
227
228    #[test]
229    fn rjumpv() {
230        let table = make_instruction_table::<_, PragueSpec>();
231        let mut host = DummyHost::default();
232        let mut interp = Interpreter::new_bytecode(Bytecode::LegacyRaw(Bytes::from([
233            RJUMPV,
234            0x01, // max index, 0 and 1
235            0x00, // first x0001
236            0x01,
237            0x00, // second 0x002
238            0x02,
239            NOP,
240            NOP,
241            NOP,
242            RJUMP,
243            0xFF,
244            (-12i8) as u8,
245            STOP,
246        ])));
247        interp.is_eof = true;
248        interp.gas = Gas::new(1000);
249
250        // more then max_index
251        interp.stack.push(U256::from(10)).unwrap();
252        interp.step(&table, &mut host);
253        assert_eq!(interp.program_counter(), 6);
254
255        // cleanup
256        interp.step(&table, &mut host);
257        interp.step(&table, &mut host);
258        interp.step(&table, &mut host);
259        interp.step(&table, &mut host);
260        assert_eq!(interp.program_counter(), 0);
261
262        // jump to first index of vtable
263        interp.stack.push(U256::from(0)).unwrap();
264        interp.step(&table, &mut host);
265        assert_eq!(interp.program_counter(), 7);
266
267        // cleanup
268        interp.step(&table, &mut host);
269        interp.step(&table, &mut host);
270        interp.step(&table, &mut host);
271        assert_eq!(interp.program_counter(), 0);
272
273        // jump to second index of vtable
274        interp.stack.push(U256::from(1)).unwrap();
275        interp.step(&table, &mut host);
276        assert_eq!(interp.program_counter(), 8);
277    }
278
279    fn dummy_eof() -> Eof {
280        let bytes = bytes!("ef000101000402000100010400000000800000fe");
281        Eof::decode(bytes).unwrap()
282    }
283
284    #[test]
285    fn callf_retf_jumpf() {
286        let table = make_instruction_table::<_, PragueSpec>();
287        let mut host = DummyHost::default();
288        let mut eof = dummy_eof();
289
290        eof.body.code_section.clear();
291        eof.header.code_sizes.clear();
292
293        let bytes1 = Bytes::from([CALLF, 0x00, 0x01, JUMPF, 0x00, 0x01]);
294        eof.header.code_sizes.push(bytes1.len() as u16);
295        eof.body.code_section.push(bytes1.clone());
296        let bytes2 = Bytes::from([STOP, RETF]);
297        eof.header.code_sizes.push(bytes2.len() as u16);
298        eof.body.code_section.push(bytes2.clone());
299
300        let mut interp = Interpreter::new_bytecode(Bytecode::Eof(eof));
301        interp.gas = Gas::new(10000);
302
303        assert_eq!(interp.function_stack.current_code_idx, 0);
304        assert!(interp.function_stack.return_stack.is_empty());
305
306        // CALLF
307        interp.step(&table, &mut host);
308
309        assert_eq!(interp.function_stack.current_code_idx, 1);
310        assert_eq!(
311            interp.function_stack.return_stack[0],
312            FunctionReturnFrame::new(0, 3)
313        );
314        assert_eq!(interp.instruction_pointer, bytes2.as_ptr());
315
316        // STOP
317        interp.step(&table, &mut host);
318        // RETF
319        interp.step(&table, &mut host);
320
321        assert_eq!(interp.function_stack.current_code_idx, 0);
322        assert_eq!(interp.function_stack.return_stack, Vec::new());
323        assert_eq!(interp.program_counter(), 3);
324
325        // JUMPF
326        interp.step(&table, &mut host);
327        assert_eq!(interp.function_stack.current_code_idx, 1);
328        assert_eq!(interp.function_stack.return_stack, Vec::new());
329        assert_eq!(interp.instruction_pointer, bytes2.as_ptr());
330    }
331}