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        contract::{
16            balance_decrease,
17            balance_increase,
18            contract_size,
19        },
20        gas::{
21            dependent_gas_charge_without_base,
22            gas_charge,
23            ProfileGas,
24        },
25        internal::{
26            current_contract,
27            external_asset_id_balance_sub,
28            inc_pc,
29            internal_contract,
30            set_frame_pointer,
31        },
32        receipts::ReceiptsCtx,
33        ExecutableTransaction,
34        InputContracts,
35        Interpreter,
36        Memory,
37        MemoryInstance,
38        PanicContext,
39        RuntimeBalances,
40    },
41    prelude::{
42        Bug,
43        BugVariant,
44    },
45    profiler::Profiler,
46    storage::{
47        ContractsAssetsStorage,
48        ContractsRawCode,
49        InterpreterStorage,
50    },
51};
52use alloc::vec::Vec;
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    bytes::padded_len_usize,
71    canonical::Serialize,
72    AssetId,
73    Bytes32,
74    ContractId,
75    Word,
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> Interpreter<M, S, Tx, Ecal>
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 = $is + address`
257    Absolute,
258    /// `$pc = $pc + address`
259    RelativeForwards,
260    /// `$pc = $pc - address`
261    RelativeBackwards,
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
265pub struct JumpArgs {
266    /// Condition. The jump is performed only if this is true.
267    condition: bool,
268    /// The kind of jump performed
269    mode: JumpMode,
270    /// Dynamic part of the jump target, i.e. register value
271    dynamic: Word,
272    /// Fixed part of the jump target, i.e. immediate value
273    fixed: Word,
274}
275
276impl JumpArgs {
277    pub(crate) fn new(mode: JumpMode) -> Self {
278        Self {
279            condition: true,
280            mode,
281            dynamic: 0,
282            fixed: 0,
283        }
284    }
285
286    pub(crate) fn with_condition(mut self, condition: bool) -> Self {
287        self.condition = condition;
288        self
289    }
290
291    pub(crate) fn to_address(mut self, addr: Word) -> Self {
292        self.dynamic = addr;
293        self
294    }
295
296    pub(crate) fn plus_fixed(mut self, addr: Word) -> Self {
297        self.fixed = addr;
298        self
299    }
300
301    pub(crate) fn jump(&self, is: Reg<IS>, mut pc: RegMut<PC>) -> SimpleResult<()> {
302        if !self.condition {
303            return Ok(inc_pc(pc)?)
304        }
305
306        let offset_instructions = match self.mode {
307            JumpMode::Absolute => self.dynamic.saturating_add(self.fixed),
308            // Here +1 is added since jumping to the jump instruction itself doesn't make
309            // sense
310            JumpMode::RelativeForwards | JumpMode::RelativeBackwards => {
311                self.dynamic.saturating_add(self.fixed).saturating_add(1)
312            }
313        };
314
315        let offset_bytes = offset_instructions.saturating_mul(Instruction::SIZE as Word);
316
317        let target_addr = match self.mode {
318            JumpMode::Absolute => is.saturating_add(offset_bytes),
319            JumpMode::RelativeForwards => pc.saturating_add(offset_bytes),
320            JumpMode::RelativeBackwards => pc
321                .checked_sub(offset_bytes)
322                .ok_or(PanicReason::MemoryOverflow)?,
323        };
324
325        if target_addr >= VM_MAX_RAM {
326            return Err(PanicReason::MemoryOverflow.into())
327        }
328
329        *pc = target_addr;
330        Ok(())
331    }
332}
333
334impl<M, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal>
335where
336    M: Memory,
337    S: InterpreterStorage,
338    Tx: ExecutableTransaction,
339{
340    /// Prepare a call instruction for execution
341    pub fn prepare_call(
342        &mut self,
343        ra: RegId,
344        rb: RegId,
345        rc: RegId,
346        rd: RegId,
347    ) -> IoResult<(), S::DataError> {
348        self.prepare_call_inner(
349            self.registers[ra],
350            self.registers[rb],
351            self.registers[rc],
352            self.registers[rd],
353        )
354    }
355
356    /// Prepare a call instruction for execution
357    fn prepare_call_inner(
358        &mut self,
359        call_params_pointer: Word,
360        amount_of_coins_to_forward: Word,
361        asset_id_pointer: Word,
362        amount_of_gas_to_forward: Word,
363    ) -> IoResult<(), S::DataError> {
364        let params = PrepareCallParams {
365            call_params_pointer,
366            asset_id_pointer,
367            amount_of_coins_to_forward,
368            amount_of_gas_to_forward,
369        };
370        let gas_cost = self.gas_costs().call();
371        let new_storage_gas_per_byte = self.gas_costs().new_storage_per_byte();
372        // Charge only for the `base` execution.
373        // We will charge for the frame size in the `prepare_call`.
374        self.gas_charge(gas_cost.base())?;
375        let current_contract =
376            current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?;
377
378        PrepareCallCtx {
379            params,
380            registers: (&mut self.registers).into(),
381            memory: self.memory.as_mut(),
382            context: &mut self.context,
383            gas_cost,
384            runtime_balances: &mut self.balances,
385            storage: &mut self.storage,
386            input_contracts: InputContracts::new(
387                &self.input_contracts,
388                &mut self.panic_context,
389            ),
390            new_storage_gas_per_byte,
391            receipts: &mut self.receipts,
392            frames: &mut self.frames,
393            current_contract,
394            profiler: &mut self.profiler,
395        }
396        .prepare_call()
397    }
398}
399
400#[cfg_attr(test, derive(Default))]
401struct PrepareCallParams {
402    /// Register A of input
403    pub call_params_pointer: Word,
404    /// Register B of input
405    pub amount_of_coins_to_forward: Word,
406    /// Register C of input
407    pub asset_id_pointer: Word,
408    /// Register D of input
409    pub amount_of_gas_to_forward: Word,
410}
411
412struct PrepareCallSystemRegisters<'a> {
413    hp: Reg<'a, HP>,
414    sp: RegMut<'a, SP>,
415    ssp: RegMut<'a, SSP>,
416    fp: RegMut<'a, FP>,
417    pc: RegMut<'a, PC>,
418    is: RegMut<'a, IS>,
419    bal: RegMut<'a, BAL>,
420    cgas: RegMut<'a, CGAS>,
421    ggas: RegMut<'a, GGAS>,
422    flag: RegMut<'a, FLAG>,
423}
424
425struct PrepareCallRegisters<'a> {
426    system_registers: PrepareCallSystemRegisters<'a>,
427    program_registers: ProgramRegistersRef<'a>,
428    unused_registers: PrepareCallUnusedRegisters<'a>,
429}
430
431struct PrepareCallUnusedRegisters<'a> {
432    zero: Reg<'a, ZERO>,
433    one: Reg<'a, ONE>,
434    of: Reg<'a, OF>,
435    err: Reg<'a, ERR>,
436    ret: Reg<'a, RET>,
437    retl: Reg<'a, RETL>,
438}
439
440impl<'a> PrepareCallRegisters<'a> {
441    fn copy_registers(&self) -> [Word; VM_REGISTER_COUNT] {
442        copy_registers(&self.into(), &self.program_registers)
443    }
444}
445
446struct PrepareCallCtx<'vm, S> {
447    params: PrepareCallParams,
448    registers: PrepareCallRegisters<'vm>,
449    memory: &'vm mut MemoryInstance,
450    context: &'vm mut Context,
451    gas_cost: DependentCost,
452    runtime_balances: &'vm mut RuntimeBalances,
453    new_storage_gas_per_byte: Word,
454    storage: &'vm mut S,
455    input_contracts: InputContracts<'vm>,
456    receipts: &'vm mut ReceiptsCtx,
457    frames: &'vm mut Vec<CallFrame>,
458    current_contract: Option<ContractId>,
459    profiler: &'vm mut Profiler,
460}
461
462impl<'vm, S> PrepareCallCtx<'vm, S>
463where
464    S: InterpreterStorage,
465{
466    fn prepare_call(mut self) -> IoResult<(), S::DataError>
467    where
468        S: StorageSize<ContractsRawCode>
469            + ContractsAssetsStorage
470            + StorageRead<ContractsRawCode>
471            + StorageAsRef,
472    {
473        let call_bytes = self
474            .memory
475            .read(self.params.call_params_pointer, Call::LEN)?;
476        let call = Call::try_from(call_bytes)?;
477        let asset_id =
478            AssetId::new(self.memory.read_bytes(self.params.asset_id_pointer)?);
479
480        let code_size = contract_size(&self.storage, call.to())? as usize;
481        let code_size_padded =
482            padded_len_usize(code_size).ok_or(PanicReason::MemoryOverflow)?;
483
484        let total_size_in_stack = CallFrame::serialized_size()
485            .checked_add(code_size_padded)
486            .ok_or_else(|| Bug::new(BugVariant::CodeSizeOverflow))?;
487
488        let profiler = ProfileGas {
489            pc: self.registers.system_registers.pc.as_ref(),
490            is: self.registers.system_registers.is.as_ref(),
491            current_contract: self.current_contract,
492            profiler: self.profiler,
493        };
494        dependent_gas_charge_without_base(
495            self.registers.system_registers.cgas.as_mut(),
496            self.registers.system_registers.ggas.as_mut(),
497            profiler,
498            self.gas_cost,
499            code_size_padded as Word,
500        )?;
501
502        if let Some(source_contract) = self.current_contract {
503            balance_decrease(
504                self.storage,
505                &source_contract,
506                &asset_id,
507                self.params.amount_of_coins_to_forward,
508            )?;
509        } else {
510            let amount = self.params.amount_of_coins_to_forward;
511            external_asset_id_balance_sub(
512                self.runtime_balances,
513                self.memory,
514                &asset_id,
515                amount,
516            )?;
517        }
518
519        self.input_contracts.check(call.to())?;
520
521        // credit contract asset_id balance
522        let (_, created_new_entry) = balance_increase(
523            self.storage,
524            call.to(),
525            &asset_id,
526            self.params.amount_of_coins_to_forward,
527        )?;
528
529        if created_new_entry {
530            // If a new entry was created, we must charge gas for it
531            let profiler = ProfileGas {
532                pc: self.registers.system_registers.pc.as_ref(),
533                is: self.registers.system_registers.is.as_ref(),
534                current_contract: self.current_contract,
535                profiler: self.profiler,
536            };
537            gas_charge(
538                self.registers.system_registers.cgas.as_mut(),
539                self.registers.system_registers.ggas.as_mut(),
540                profiler,
541                ((Bytes32::LEN + WORD_SIZE) as u64)
542                    .saturating_mul(self.new_storage_gas_per_byte),
543            )?;
544        }
545
546        let forward_gas_amount = cmp::min(
547            *self.registers.system_registers.cgas,
548            self.params.amount_of_gas_to_forward,
549        );
550
551        // subtract gas
552        *self.registers.system_registers.cgas = (*self.registers.system_registers.cgas)
553            .checked_sub(forward_gas_amount)
554            .ok_or_else(|| Bug::new(BugVariant::ContextGasUnderflow))?;
555
556        // Construct frame
557        let mut frame = CallFrame::new(
558            *call.to(),
559            asset_id,
560            self.registers.copy_registers(),
561            code_size_padded,
562            call.a(),
563            call.b(),
564        )
565        .ok_or(PanicReason::MemoryOverflow)?;
566        *frame.context_gas_mut() = *self.registers.system_registers.cgas;
567        *frame.global_gas_mut() = *self.registers.system_registers.ggas;
568
569        // Allocate stack memory
570        let old_sp = *self.registers.system_registers.sp;
571        let new_sp = old_sp.saturating_add(total_size_in_stack as Word);
572        self.memory.grow_stack(new_sp)?;
573        *self.registers.system_registers.sp = new_sp;
574        *self.registers.system_registers.ssp = new_sp;
575
576        let id = internal_contract(
577            self.context,
578            self.registers.system_registers.fp.as_ref(),
579            self.memory,
580        )
581        .unwrap_or_default();
582
583        set_frame_pointer(
584            self.context,
585            self.registers.system_registers.fp.as_mut(),
586            old_sp,
587        );
588
589        // Write the frame to memory
590        // Ownership checks are disabled because we just allocated the memory above.
591        let dst = self.memory.write_noownerchecks(
592            *self.registers.system_registers.fp,
593            total_size_in_stack,
594        )?;
595        let (mem_frame, mem_code) = dst.split_at_mut(CallFrame::serialized_size());
596        mem_frame.copy_from_slice(&frame.to_bytes());
597        let (mem_code, mem_code_padding) = mem_code.split_at_mut(code_size);
598        read_contract(call.to(), self.storage, mem_code)?;
599        mem_code_padding.fill(0);
600
601        #[allow(clippy::arithmetic_side_effects)] // Checked above
602        let code_start =
603            (*self.registers.system_registers.fp) + CallFrame::serialized_size() as Word;
604
605        *self.registers.system_registers.pc = code_start;
606        *self.registers.system_registers.bal = self.params.amount_of_coins_to_forward;
607        *self.registers.system_registers.is = *self.registers.system_registers.pc;
608        *self.registers.system_registers.cgas = forward_gas_amount;
609        *self.registers.system_registers.flag = 0;
610
611        let receipt = Receipt::call(
612            id,
613            *call.to(),
614            self.params.amount_of_coins_to_forward,
615            asset_id,
616            forward_gas_amount,
617            call.a(),
618            call.b(),
619            *self.registers.system_registers.pc,
620            *self.registers.system_registers.is,
621        );
622
623        self.receipts.push(receipt)?;
624
625        self.frames.push(frame);
626
627        Ok(())
628    }
629}
630
631fn read_contract<S>(
632    contract: &ContractId,
633    storage: &S,
634    dst: &mut [u8],
635) -> IoResult<(), S::Error>
636where
637    S: StorageSize<ContractsRawCode> + StorageRead<ContractsRawCode> + StorageAsRef,
638{
639    let bytes_read = storage
640        .storage::<ContractsRawCode>()
641        .read(contract, 0, dst)
642        .map_err(RuntimeError::Storage)?
643        .ok_or(PanicReason::ContractNotFound)?;
644    if bytes_read != dst.len() {
645        return Err(PanicReason::ContractMismatch.into())
646    }
647    Ok(())
648}
649
650impl<'a> From<&'a PrepareCallRegisters<'_>> for SystemRegistersRef<'a> {
651    fn from(registers: &'a PrepareCallRegisters) -> Self {
652        Self {
653            hp: registers.system_registers.hp,
654            sp: registers.system_registers.sp.as_ref(),
655            ssp: registers.system_registers.ssp.as_ref(),
656            fp: registers.system_registers.fp.as_ref(),
657            pc: registers.system_registers.pc.as_ref(),
658            is: registers.system_registers.is.as_ref(),
659            bal: registers.system_registers.bal.as_ref(),
660            cgas: registers.system_registers.cgas.as_ref(),
661            ggas: registers.system_registers.ggas.as_ref(),
662            flag: registers.system_registers.flag.as_ref(),
663            zero: registers.unused_registers.zero,
664            one: registers.unused_registers.one,
665            of: registers.unused_registers.of,
666            err: registers.unused_registers.err,
667            ret: registers.unused_registers.ret,
668            retl: registers.unused_registers.retl,
669        }
670    }
671}
672
673impl<'reg> From<&'reg mut [Word; VM_REGISTER_COUNT]> for PrepareCallRegisters<'reg> {
674    fn from(registers: &'reg mut [Word; VM_REGISTER_COUNT]) -> Self {
675        let (r, w) = split_registers(registers);
676        let (r, u) = r.into();
677        Self {
678            system_registers: r,
679            program_registers: w.into(),
680            unused_registers: u,
681        }
682    }
683}
684
685impl<'reg> From<SystemRegisters<'reg>>
686    for (
687        PrepareCallSystemRegisters<'reg>,
688        PrepareCallUnusedRegisters<'reg>,
689    )
690{
691    fn from(registers: SystemRegisters<'reg>) -> Self {
692        let read = PrepareCallSystemRegisters {
693            hp: registers.hp.into(),
694            sp: registers.sp,
695            ssp: registers.ssp,
696            fp: registers.fp,
697            pc: registers.pc,
698            is: registers.is,
699            bal: registers.bal,
700            cgas: registers.cgas,
701            ggas: registers.ggas,
702            flag: registers.flag,
703        };
704
705        (
706            read,
707            PrepareCallUnusedRegisters {
708                zero: registers.zero.into(),
709                one: registers.one.into(),
710                of: registers.of.into(),
711                err: registers.err.into(),
712                ret: registers.ret.into(),
713                retl: registers.retl.into(),
714            },
715        )
716    }
717}