fuel_vm/interpreter/
flow.rs

1use crate::{
2    call::{
3        Call,
4        CallFrame,
5    },
6    constraints::reg_key::*,
7    consts::*,
8    context::Context,
9    error::{
10        IoResult,
11        RuntimeError,
12        SimpleResult,
13    },
14    interpreter::{
15        ExecutableTransaction,
16        Interpreter,
17        Memory,
18        MemoryInstance,
19        PanicContext,
20        RuntimeBalances,
21        contract::{
22            balance_decrease,
23            balance_increase,
24            contract_size,
25        },
26        gas::{
27            dependent_gas_charge_without_base,
28            gas_charge,
29        },
30        internal::{
31            current_contract,
32            external_asset_id_balance_sub,
33            inc_pc,
34            internal_contract,
35            set_frame_pointer,
36        },
37        receipts::ReceiptsCtx,
38    },
39    prelude::{
40        Bug,
41        BugVariant,
42    },
43    storage::{
44        ContractsRawCode,
45        InterpreterStorage,
46    },
47    verification::Verifier,
48};
49use alloc::{
50    collections::BTreeSet,
51    vec::Vec,
52};
53use core::cmp;
54use fuel_asm::{
55    Instruction,
56    PanicInstruction,
57    RegId,
58};
59use fuel_storage::{
60    StorageAsRef,
61    StorageRead,
62    StorageSize,
63};
64use fuel_tx::{
65    DependentCost,
66    PanicReason,
67    Receipt,
68};
69use fuel_types::{
70    AssetId,
71    Bytes32,
72    ContractId,
73    Word,
74    bytes::padded_len_usize,
75    canonical::Serialize,
76};
77
78#[cfg(test)]
79mod jump_tests;
80#[cfg(test)]
81mod ret_tests;
82#[cfg(test)]
83mod tests;
84
85impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
86where
87    M: Memory,
88    Tx: ExecutableTransaction,
89{
90    pub(crate) fn jump(&mut self, args: JumpArgs) -> SimpleResult<()> {
91        let (SystemRegisters { pc, is, .. }, _) = split_registers(&mut self.registers);
92        args.jump(is.as_ref(), pc)
93    }
94
95    pub(crate) fn ret(&mut self, a: Word) -> SimpleResult<()> {
96        let current_contract =
97            current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?;
98        let input = RetCtx {
99            receipts: &mut self.receipts,
100            frames: &mut self.frames,
101            registers: &mut self.registers,
102            memory: self.memory.as_ref(),
103            context: &mut self.context,
104            current_contract,
105        };
106        input.ret(a)
107    }
108
109    pub(crate) fn ret_data(&mut self, a: Word, b: Word) -> SimpleResult<Bytes32> {
110        let current_contract =
111            current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?;
112        let input = RetCtx {
113            frames: &mut self.frames,
114            registers: &mut self.registers,
115            memory: self.memory.as_mut(),
116            receipts: &mut self.receipts,
117            context: &mut self.context,
118            current_contract,
119        };
120        input.ret_data(a, b)
121    }
122
123    pub(crate) fn revert(&mut self, a: Word) -> SimpleResult<()> {
124        let current_contract =
125            current_contract(&self.context, self.registers.fp(), self.memory.as_ref())
126                .unwrap_or(Some(ContractId::zeroed()));
127        revert(
128            &mut self.receipts,
129            current_contract,
130            self.registers.pc(),
131            self.registers.is(),
132            a,
133        )
134    }
135
136    pub(crate) fn append_panic_receipt(&mut self, result: PanicInstruction) {
137        let pc = self.registers[RegId::PC];
138        let is = self.registers[RegId::IS];
139
140        let mut receipt =
141            Receipt::panic(self.internal_contract().unwrap_or_default(), result, pc, is);
142
143        match self.panic_context {
144            PanicContext::None => {}
145            PanicContext::ContractId(contract_id) => {
146                receipt = receipt.with_panic_contract_id(Some(contract_id));
147            }
148        };
149        self.panic_context = PanicContext::None;
150
151        self.receipts
152            .push(receipt)
153            .expect("Appending a panic receipt cannot fail");
154    }
155}
156
157struct RetCtx<'vm> {
158    frames: &'vm mut Vec<CallFrame>,
159    registers: &'vm mut [Word; VM_REGISTER_COUNT],
160    memory: &'vm MemoryInstance,
161    receipts: &'vm mut ReceiptsCtx,
162    context: &'vm mut Context,
163    current_contract: Option<ContractId>,
164}
165
166impl RetCtx<'_> {
167    pub(crate) fn ret(self, a: Word) -> SimpleResult<()> {
168        let receipt = Receipt::ret(
169            self.current_contract.unwrap_or_else(ContractId::zeroed),
170            a,
171            self.registers[RegId::PC],
172            self.registers[RegId::IS],
173        );
174
175        self.registers[RegId::RET] = a;
176        self.registers[RegId::RETL] = 0;
177
178        // TODO if ret instruction is in memory boundary, inc_pc shouldn't fail
179        self.return_from_context(receipt)
180    }
181
182    pub(crate) fn return_from_context(mut self, receipt: Receipt) -> SimpleResult<()> {
183        if let Some(frame) = self.frames.pop() {
184            let registers = &mut self.registers;
185            let context = &mut self.context;
186
187            registers[RegId::CGAS] = registers[RegId::CGAS]
188                .checked_add(frame.context_gas())
189                .ok_or_else(|| Bug::new(BugVariant::ContextGasOverflow))?;
190
191            let cgas = registers[RegId::CGAS];
192            let ggas = registers[RegId::GGAS];
193            let ret = registers[RegId::RET];
194            let retl = registers[RegId::RETL];
195            let hp = registers[RegId::HP];
196
197            registers.copy_from_slice(frame.registers());
198
199            registers[RegId::CGAS] = cgas;
200            registers[RegId::GGAS] = ggas;
201            registers[RegId::RET] = ret;
202            registers[RegId::RETL] = retl;
203            registers[RegId::HP] = hp;
204
205            let fp = registers[RegId::FP];
206            set_frame_pointer(context, registers.fp_mut(), fp);
207        }
208
209        self.receipts.push(receipt)?;
210
211        Ok(inc_pc(self.registers.pc_mut())?)
212    }
213
214    pub(crate) fn ret_data(self, a: Word, b: Word) -> SimpleResult<Bytes32> {
215        let data = self.memory.read(a, b)?.to_vec();
216
217        let receipt = Receipt::return_data(
218            self.current_contract.unwrap_or_else(ContractId::zeroed),
219            a,
220            self.registers[RegId::PC],
221            self.registers[RegId::IS],
222            data,
223        );
224        let digest = *receipt
225            .digest()
226            .expect("Receipt is created above and `digest` should exist");
227
228        self.registers[RegId::RET] = a;
229        self.registers[RegId::RETL] = b;
230
231        self.return_from_context(receipt)?;
232
233        Ok(digest)
234    }
235}
236
237pub(crate) fn revert(
238    receipts: &mut ReceiptsCtx,
239    current_contract: Option<ContractId>,
240    pc: Reg<PC>,
241    is: Reg<IS>,
242    a: Word,
243) -> SimpleResult<()> {
244    let receipt = Receipt::revert(
245        current_contract.unwrap_or_else(ContractId::zeroed),
246        a,
247        *pc,
248        *is,
249    );
250
251    receipts.push(receipt)
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
255pub enum JumpMode {
256    /// `$pc = reg + (imm * instruction_size)`
257    Assign,
258    /// `$pc = $is + (reg + imm) * instruction_size)`
259    RelativeIS,
260    /// `$pc = $pc + (reg + imm + 1) * instruction_size`
261    RelativeForwards,
262    /// `$pc = $pc - (reg + imm + 1) * instruction_size`
263    RelativeBackwards,
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
267pub struct JumpArgs {
268    /// Condition. The jump is performed only if this is true.
269    condition: bool,
270    /// The kind of jump performed
271    mode: JumpMode,
272    /// Dynamic part of the jump target, i.e. register value
273    dynamic: Word,
274    /// Fixed part of the jump target, i.e. immediate value
275    fixed: Word,
276}
277
278impl JumpArgs {
279    pub(crate) fn new(mode: JumpMode) -> Self {
280        Self {
281            condition: true,
282            mode,
283            dynamic: 0,
284            fixed: 0,
285        }
286    }
287
288    pub(crate) fn with_condition(mut self, condition: bool) -> Self {
289        self.condition = condition;
290        self
291    }
292
293    pub(crate) fn to_address(mut self, addr: Word) -> Self {
294        self.dynamic = addr;
295        self
296    }
297
298    pub(crate) fn plus_fixed(mut self, addr: Word) -> Self {
299        self.fixed = addr;
300        self
301    }
302
303    pub(crate) fn jump(&self, is: Reg<IS>, mut pc: RegMut<PC>) -> SimpleResult<()> {
304        if !self.condition {
305            return Ok(inc_pc(pc)?)
306        }
307
308        let target_addr = match self.mode {
309            JumpMode::Assign => self
310                .dynamic
311                .saturating_add(self.fixed.saturating_mul(Instruction::SIZE as Word)),
312            JumpMode::RelativeIS => {
313                let offset_instructions = self.dynamic.saturating_add(self.fixed);
314                let offset_bytes =
315                    offset_instructions.saturating_mul(Instruction::SIZE as Word);
316                is.saturating_add(offset_bytes)
317            }
318            // In relative jumps, +1 is added since jumping to the jump instruction itself
319            // is not useful
320            JumpMode::RelativeForwards => {
321                let offset_instructions =
322                    self.dynamic.saturating_add(self.fixed).saturating_add(1);
323                let offset_bytes =
324                    offset_instructions.saturating_mul(Instruction::SIZE as Word);
325                pc.saturating_add(offset_bytes)
326            }
327            JumpMode::RelativeBackwards => {
328                let offset_instructions =
329                    self.dynamic.saturating_add(self.fixed).saturating_add(1);
330                let offset_bytes =
331                    offset_instructions.saturating_mul(Instruction::SIZE as Word);
332                pc.checked_sub(offset_bytes)
333                    .ok_or(PanicReason::MemoryOverflow)?
334            }
335        };
336
337        if target_addr >= VM_MAX_RAM {
338            return Err(PanicReason::MemoryOverflow.into())
339        }
340
341        *pc = target_addr;
342        Ok(())
343    }
344}
345
346impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
347where
348    M: Memory,
349    S: InterpreterStorage,
350    Tx: ExecutableTransaction,
351    V: Verifier,
352{
353    /// Prepare a call instruction for execution
354    pub fn prepare_call(
355        &mut self,
356        ra: RegId,
357        rb: RegId,
358        rc: RegId,
359        rd: RegId,
360    ) -> IoResult<(), S::DataError> {
361        self.prepare_call_inner(
362            self.registers[ra],
363            self.registers[rb],
364            self.registers[rc],
365            self.registers[rd],
366        )
367    }
368
369    /// Prepare a call instruction for execution
370    fn prepare_call_inner(
371        &mut self,
372        call_params_pointer: Word,
373        amount_of_coins_to_forward: Word,
374        asset_id_pointer: Word,
375        amount_of_gas_to_forward: Word,
376    ) -> IoResult<(), S::DataError> {
377        let params = PrepareCallParams {
378            call_params_pointer,
379            asset_id_pointer,
380            amount_of_coins_to_forward,
381            amount_of_gas_to_forward,
382        };
383        let gas_cost = self.gas_costs().call();
384        let new_storage_gas_per_byte = self.gas_costs().new_storage_per_byte();
385        // Charge only for the `base` execution.
386        // We will charge for the frame size in the `prepare_call`.
387        self.gas_charge(gas_cost.base())?;
388        let current_contract =
389            current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?;
390
391        PrepareCallCtx {
392            params,
393            registers: (&mut self.registers).into(),
394            memory: self.memory.as_mut(),
395            context: &mut self.context,
396            gas_cost,
397            runtime_balances: &mut self.balances,
398            storage: &mut self.storage,
399            input_contracts: &self.input_contracts,
400            panic_context: &mut self.panic_context,
401            new_storage_gas_per_byte,
402            receipts: &mut self.receipts,
403            frames: &mut self.frames,
404            current_contract,
405            verifier: &mut self.verifier,
406        }
407        .prepare_call()
408    }
409}
410
411#[cfg_attr(test, derive(Default))]
412struct PrepareCallParams {
413    /// Register A of input
414    pub call_params_pointer: Word,
415    /// Register B of input
416    pub amount_of_coins_to_forward: Word,
417    /// Register C of input
418    pub asset_id_pointer: Word,
419    /// Register D of input
420    pub amount_of_gas_to_forward: Word,
421}
422
423struct PrepareCallSystemRegisters<'a> {
424    hp: Reg<'a, HP>,
425    sp: RegMut<'a, SP>,
426    ssp: RegMut<'a, SSP>,
427    fp: RegMut<'a, FP>,
428    pc: RegMut<'a, PC>,
429    is: RegMut<'a, IS>,
430    bal: RegMut<'a, BAL>,
431    cgas: RegMut<'a, CGAS>,
432    ggas: RegMut<'a, GGAS>,
433    flag: RegMut<'a, FLAG>,
434}
435
436struct PrepareCallRegisters<'a> {
437    system_registers: PrepareCallSystemRegisters<'a>,
438    program_registers: ProgramRegistersRef<'a>,
439    unused_registers: PrepareCallUnusedRegisters<'a>,
440}
441
442struct PrepareCallUnusedRegisters<'a> {
443    zero: Reg<'a, ZERO>,
444    one: Reg<'a, ONE>,
445    of: Reg<'a, OF>,
446    err: Reg<'a, ERR>,
447    ret: Reg<'a, RET>,
448    retl: Reg<'a, RETL>,
449}
450
451impl PrepareCallRegisters<'_> {
452    fn copy_registers(&self) -> [Word; VM_REGISTER_COUNT] {
453        copy_registers(&self.into(), &self.program_registers)
454    }
455}
456
457struct PrepareCallCtx<'vm, S, V> {
458    params: PrepareCallParams,
459    registers: PrepareCallRegisters<'vm>,
460    memory: &'vm mut MemoryInstance,
461    context: &'vm mut Context,
462    gas_cost: DependentCost,
463    runtime_balances: &'vm mut RuntimeBalances,
464    new_storage_gas_per_byte: Word,
465    storage: &'vm mut S,
466    input_contracts: &'vm BTreeSet<ContractId>,
467    panic_context: &'vm mut PanicContext,
468    receipts: &'vm mut ReceiptsCtx,
469    frames: &'vm mut Vec<CallFrame>,
470    current_contract: Option<ContractId>,
471    verifier: &'vm mut V,
472}
473
474impl<S, V> PrepareCallCtx<'_, S, V> {
475    fn prepare_call(mut self) -> IoResult<(), S::DataError>
476    where
477        S: InterpreterStorage,
478        V: Verifier,
479    {
480        let call_bytes = self
481            .memory
482            .read(self.params.call_params_pointer, Call::LEN)?;
483        let call = Call::try_from(call_bytes)?;
484        let asset_id =
485            AssetId::new(self.memory.read_bytes(self.params.asset_id_pointer)?);
486
487        let code_size = contract_size(&self.storage, call.to())? as usize;
488        let code_size_padded =
489            padded_len_usize(code_size).ok_or(PanicReason::MemoryOverflow)?;
490
491        let total_size_in_stack = CallFrame::serialized_size()
492            .checked_add(code_size_padded)
493            .ok_or_else(|| Bug::new(BugVariant::CodeSizeOverflow))?;
494
495        dependent_gas_charge_without_base(
496            self.registers.system_registers.cgas.as_mut(),
497            self.registers.system_registers.ggas.as_mut(),
498            self.gas_cost,
499            code_size_padded as Word,
500        )?;
501
502        let amount = self.params.amount_of_coins_to_forward;
503        if let Some(source_contract) = self.current_contract {
504            balance_decrease(self.storage, &source_contract, &asset_id, amount)?;
505        } else {
506            external_asset_id_balance_sub(
507                self.runtime_balances,
508                self.memory,
509                &asset_id,
510                amount,
511            )?;
512        }
513
514        self.verifier.check_contract_in_inputs(
515            self.panic_context,
516            self.input_contracts,
517            call.to(),
518        )?;
519
520        // credit contract asset_id balance
521        let created_new_entry = balance_increase(
522            self.storage,
523            call.to(),
524            &asset_id,
525            self.params.amount_of_coins_to_forward,
526        )?;
527
528        if created_new_entry {
529            // If a new entry was created, we must charge gas for it
530            gas_charge(
531                self.registers.system_registers.cgas.as_mut(),
532                self.registers.system_registers.ggas.as_mut(),
533                ((Bytes32::LEN + WORD_SIZE) as u64)
534                    .saturating_mul(self.new_storage_gas_per_byte),
535            )?;
536        }
537
538        let forward_gas_amount = cmp::min(
539            *self.registers.system_registers.cgas,
540            self.params.amount_of_gas_to_forward,
541        );
542
543        // subtract gas
544        *self.registers.system_registers.cgas = (*self.registers.system_registers.cgas)
545            .checked_sub(forward_gas_amount)
546            .ok_or_else(|| Bug::new(BugVariant::ContextGasUnderflow))?;
547
548        // Construct frame
549        let mut frame = CallFrame::new(
550            *call.to(),
551            asset_id,
552            self.registers.copy_registers(),
553            code_size_padded,
554            call.a(),
555            call.b(),
556        )
557        .ok_or(PanicReason::MemoryOverflow)?;
558        *frame.context_gas_mut() = *self.registers.system_registers.cgas;
559        *frame.global_gas_mut() = *self.registers.system_registers.ggas;
560
561        // Allocate stack memory
562        let old_sp = *self.registers.system_registers.sp;
563        let new_sp = old_sp.saturating_add(total_size_in_stack as Word);
564        self.memory.grow_stack(new_sp)?;
565        *self.registers.system_registers.sp = new_sp;
566        *self.registers.system_registers.ssp = new_sp;
567
568        let id = internal_contract(
569            self.context,
570            self.registers.system_registers.fp.as_ref(),
571            self.memory,
572        )
573        .unwrap_or_default();
574
575        set_frame_pointer(
576            self.context,
577            self.registers.system_registers.fp.as_mut(),
578            old_sp,
579        );
580
581        // Write the frame to memory
582        // Ownership checks are disabled because we just allocated the memory above.
583        let dst = self.memory.write_noownerchecks(
584            *self.registers.system_registers.fp,
585            total_size_in_stack,
586        )?;
587        let (mem_frame, mem_code) = dst.split_at_mut(CallFrame::serialized_size());
588        mem_frame.copy_from_slice(&frame.to_bytes());
589        let (mem_code, mem_code_padding) = mem_code.split_at_mut(code_size);
590        read_contract(call.to(), self.storage, mem_code)?;
591        mem_code_padding.fill(0);
592
593        #[allow(clippy::arithmetic_side_effects)] // Checked above
594        let code_start =
595            (*self.registers.system_registers.fp) + CallFrame::serialized_size() as Word;
596
597        *self.registers.system_registers.pc = code_start;
598        *self.registers.system_registers.bal = self.params.amount_of_coins_to_forward;
599        *self.registers.system_registers.is = *self.registers.system_registers.pc;
600        *self.registers.system_registers.cgas = forward_gas_amount;
601        *self.registers.system_registers.flag = 0;
602
603        let receipt = Receipt::call(
604            id,
605            *call.to(),
606            self.params.amount_of_coins_to_forward,
607            asset_id,
608            forward_gas_amount,
609            call.a(),
610            call.b(),
611            *self.registers.system_registers.pc,
612            *self.registers.system_registers.is,
613        );
614
615        self.receipts.push(receipt)?;
616
617        self.frames.push(frame);
618
619        Ok(())
620    }
621}
622
623fn read_contract<S>(
624    contract: &ContractId,
625    storage: &S,
626    dst: &mut [u8],
627) -> IoResult<(), S::Error>
628where
629    S: StorageSize<ContractsRawCode> + StorageRead<ContractsRawCode> + StorageAsRef,
630{
631    if !storage
632        .storage::<ContractsRawCode>()
633        .read(contract, 0, dst)
634        .map_err(RuntimeError::Storage)?
635    {
636        return Err(PanicReason::ContractNotFound.into());
637    }
638    Ok(())
639}
640
641impl<'a> From<&'a PrepareCallRegisters<'_>> for SystemRegistersRef<'a> {
642    fn from(registers: &'a PrepareCallRegisters) -> Self {
643        Self {
644            hp: registers.system_registers.hp,
645            sp: registers.system_registers.sp.as_ref(),
646            ssp: registers.system_registers.ssp.as_ref(),
647            fp: registers.system_registers.fp.as_ref(),
648            pc: registers.system_registers.pc.as_ref(),
649            is: registers.system_registers.is.as_ref(),
650            bal: registers.system_registers.bal.as_ref(),
651            cgas: registers.system_registers.cgas.as_ref(),
652            ggas: registers.system_registers.ggas.as_ref(),
653            flag: registers.system_registers.flag.as_ref(),
654            zero: registers.unused_registers.zero,
655            one: registers.unused_registers.one,
656            of: registers.unused_registers.of,
657            err: registers.unused_registers.err,
658            ret: registers.unused_registers.ret,
659            retl: registers.unused_registers.retl,
660        }
661    }
662}
663
664impl<'reg> From<&'reg mut [Word; VM_REGISTER_COUNT]> for PrepareCallRegisters<'reg> {
665    fn from(registers: &'reg mut [Word; VM_REGISTER_COUNT]) -> Self {
666        let (r, w) = split_registers(registers);
667        let (r, u) = r.into();
668        Self {
669            system_registers: r,
670            program_registers: w.into(),
671            unused_registers: u,
672        }
673    }
674}
675
676impl<'reg> From<SystemRegisters<'reg>>
677    for (
678        PrepareCallSystemRegisters<'reg>,
679        PrepareCallUnusedRegisters<'reg>,
680    )
681{
682    fn from(registers: SystemRegisters<'reg>) -> Self {
683        let read = PrepareCallSystemRegisters {
684            hp: registers.hp.into(),
685            sp: registers.sp,
686            ssp: registers.ssp,
687            fp: registers.fp,
688            pc: registers.pc,
689            is: registers.is,
690            bal: registers.bal,
691            cgas: registers.cgas,
692            ggas: registers.ggas,
693            flag: registers.flag,
694        };
695
696        (
697            read,
698            PrepareCallUnusedRegisters {
699                zero: registers.zero.into(),
700                one: registers.one.into(),
701                of: registers.of.into(),
702                err: registers.err.into(),
703                ret: registers.ret.into(),
704                retl: registers.retl.into(),
705            },
706        )
707    }
708}