Skip to main content

fuel_vm/interpreter/
contract.rs

1//! This module contains logic on contract management.
2
3use alloc::collections::BTreeSet;
4
5use super::{
6    ExecutableTransaction,
7    Interpreter,
8    Memory,
9    MemoryInstance,
10    PanicContext,
11    RuntimeBalances,
12    gas::gas_charge,
13    internal::{
14        external_asset_id_balance_sub,
15        inc_pc,
16        internal_contract,
17        set_variable_output,
18    },
19};
20use crate::{
21    constraints::reg_key::*,
22    consts::*,
23    context::Context,
24    convert,
25    error::{
26        IoResult,
27        RuntimeError,
28    },
29    interpreter::receipts::ReceiptsCtx,
30    storage::{
31        BlobData,
32        ContractsAssetsStorage,
33        ContractsRawCode,
34        InterpreterStorage,
35    },
36    verification::Verifier,
37};
38use fuel_asm::{
39    PanicReason,
40    RegId,
41    Word,
42};
43use fuel_storage::StorageSize;
44use fuel_tx::{
45    Output,
46    Receipt,
47};
48use fuel_types::{
49    Address,
50    AssetId,
51    BlobId,
52    Bytes32,
53    ContractId,
54};
55
56impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
57where
58    M: Memory,
59    S: InterpreterStorage,
60    Tx: ExecutableTransaction,
61    V: Verifier,
62{
63    pub(crate) fn contract_balance(
64        &mut self,
65        ra: RegId,
66        b: Word,
67        c: Word,
68    ) -> Result<(), RuntimeError<S::DataError>> {
69        let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers);
70        let result = &mut w[WriteRegKey::try_from(ra)?];
71        let input = ContractBalanceCtx {
72            storage: &self.storage,
73            memory: self.memory.as_mut(),
74            pc,
75            input_contracts: &self.input_contracts,
76            panic_context: &mut self.panic_context,
77            verifier: &mut self.verifier,
78        };
79        input.contract_balance(result, b, c)?;
80        Ok(())
81    }
82
83    pub(crate) fn transfer(
84        &mut self,
85        a: Word,
86        b: Word,
87        c: Word,
88    ) -> IoResult<(), S::DataError> {
89        let new_storage_gas_per_byte = self.gas_costs().new_storage_per_byte();
90        let tx_offset = self.tx_offset();
91        let (
92            SystemRegisters {
93                cgas,
94                ggas,
95                fp,
96                is,
97                pc,
98                ..
99            },
100            _,
101        ) = split_registers(&mut self.registers);
102        let input = TransferCtx {
103            storage: &mut self.storage,
104            memory: self.memory.as_mut(),
105            context: &self.context,
106            balances: &mut self.balances,
107            receipts: &mut self.receipts,
108            new_storage_gas_per_byte,
109            tx: &mut self.tx,
110            input_contracts: &self.input_contracts,
111            panic_context: &mut self.panic_context,
112            tx_offset,
113            cgas,
114            ggas,
115            fp: fp.as_ref(),
116            is: is.as_ref(),
117            pc,
118            verifier: &mut self.verifier,
119        };
120        input.transfer(a, b, c)
121    }
122
123    pub(crate) fn transfer_output(
124        &mut self,
125        a: Word,
126        b: Word,
127        c: Word,
128        d: Word,
129    ) -> IoResult<(), S::DataError> {
130        let tx_offset = self.tx_offset();
131        let new_storage_gas_per_byte = self.gas_costs().new_storage_per_byte();
132        let (
133            SystemRegisters {
134                cgas,
135                ggas,
136                fp,
137                is,
138                pc,
139                ..
140            },
141            _,
142        ) = split_registers(&mut self.registers);
143        let input = TransferCtx {
144            storage: &mut self.storage,
145            memory: self.memory.as_mut(),
146            context: &self.context,
147            balances: &mut self.balances,
148            receipts: &mut self.receipts,
149            new_storage_gas_per_byte,
150            tx: &mut self.tx,
151            input_contracts: &self.input_contracts,
152            panic_context: &mut self.panic_context,
153            tx_offset,
154            cgas,
155            ggas,
156            fp: fp.as_ref(),
157            is: is.as_ref(),
158            pc,
159            verifier: &mut self.verifier,
160        };
161        input.transfer_output(a, b, c, d)
162    }
163
164    pub(crate) fn check_contract_exists(
165        &self,
166        contract: &ContractId,
167    ) -> IoResult<bool, S::DataError> {
168        self.storage
169            .storage_contract_exists(contract)
170            .map_err(RuntimeError::Storage)
171    }
172}
173
174struct ContractBalanceCtx<'vm, S, V> {
175    storage: &'vm S,
176    memory: &'vm mut MemoryInstance,
177    pc: RegMut<'vm, PC>,
178    input_contracts: &'vm BTreeSet<ContractId>,
179    panic_context: &'vm mut PanicContext,
180    verifier: &'vm mut V,
181}
182
183impl<S, V> ContractBalanceCtx<'_, S, V> {
184    pub(crate) fn contract_balance(
185        self,
186        result: &mut Word,
187        b: Word,
188        c: Word,
189    ) -> IoResult<(), S::Error>
190    where
191        S: ContractsAssetsStorage,
192        V: Verifier,
193    {
194        let asset_id = AssetId::new(self.memory.read_bytes(b)?);
195        let contract_id = ContractId::new(self.memory.read_bytes(c)?);
196
197        self.verifier.check_contract_in_inputs(
198            self.panic_context,
199            self.input_contracts,
200            &contract_id,
201        )?;
202
203        let balance = balance(self.storage, &contract_id, &asset_id)?;
204
205        *result = balance;
206
207        inc_pc(self.pc);
208        Ok(())
209    }
210}
211struct TransferCtx<'vm, S, Tx, V> {
212    storage: &'vm mut S,
213    memory: &'vm mut MemoryInstance,
214    context: &'vm Context,
215    balances: &'vm mut RuntimeBalances,
216    receipts: &'vm mut ReceiptsCtx,
217
218    new_storage_gas_per_byte: Word,
219    tx: &'vm mut Tx,
220    input_contracts: &'vm BTreeSet<ContractId>,
221    panic_context: &'vm mut PanicContext,
222    tx_offset: usize,
223    cgas: RegMut<'vm, CGAS>,
224    ggas: RegMut<'vm, GGAS>,
225    fp: Reg<'vm, FP>,
226    is: Reg<'vm, IS>,
227    pc: RegMut<'vm, PC>,
228    verifier: &'vm mut V,
229}
230
231impl<S, Tx, V> TransferCtx<'_, S, Tx, V> {
232    /// In Fuel specs:
233    /// Transfer $rB coins with asset ID at $rC to contract with ID at $rA.
234    /// $rA -> recipient_contract_id_offset
235    /// $rB -> transfer_amount
236    /// $rC -> asset_id_offset
237    pub(crate) fn transfer(
238        self,
239        recipient_contract_id_offset: Word,
240        transfer_amount: Word,
241        asset_id_offset: Word,
242    ) -> IoResult<(), S::Error>
243    where
244        Tx: ExecutableTransaction,
245        S: ContractsAssetsStorage,
246        V: Verifier,
247    {
248        let amount = transfer_amount;
249        let destination =
250            ContractId::from(self.memory.read_bytes(recipient_contract_id_offset)?);
251        let asset_id = AssetId::from(self.memory.read_bytes(asset_id_offset)?);
252
253        self.verifier.check_contract_in_inputs(
254            self.panic_context,
255            self.input_contracts,
256            &destination,
257        )?;
258
259        if amount == 0 {
260            return Err(PanicReason::TransferZeroCoins.into())
261        }
262
263        let internal_context = match internal_contract(self.context, self.fp, self.memory)
264        {
265            // optimistically attempt to load the internal contract id
266            Ok(source_contract) => Some(source_contract),
267            // revert to external context if no internal contract is set
268            Err(PanicReason::ExpectedInternalContext) => None,
269            // bubble up any other kind of errors
270            Err(e) => return Err(e.into()),
271        };
272
273        if let Some(source_contract) = internal_context {
274            // debit funding source (source contract balance)
275            balance_decrease(self.storage, &source_contract, &asset_id, amount)?;
276        } else {
277            // debit external funding source (i.e. free balance)
278            external_asset_id_balance_sub(self.balances, self.memory, &asset_id, amount)?;
279        }
280        // credit destination contract
281        let created_new_entry =
282            balance_increase(self.storage, &destination, &asset_id, amount)?;
283        if created_new_entry {
284            // If a new entry was created, we must charge gas for it
285            gas_charge(
286                self.cgas,
287                self.ggas,
288                ((Bytes32::LEN + WORD_SIZE) as u64)
289                    .saturating_mul(self.new_storage_gas_per_byte),
290            )?;
291        }
292
293        let receipt = Receipt::transfer(
294            internal_context.unwrap_or_default(),
295            destination,
296            amount,
297            asset_id,
298            *self.pc,
299            *self.is,
300        );
301
302        self.receipts.push(receipt)?;
303
304        inc_pc(self.pc);
305        Ok(())
306    }
307
308    /// In Fuel specs:
309    /// Transfer $rC coins with asset ID at $rD to address at $rA, with output $rB.
310    /// $rA -> recipient_offset
311    /// $rB -> output_index
312    /// $rC -> transfer_amount
313    /// $rD -> asset_id_offset
314    pub(crate) fn transfer_output(
315        self,
316        recipient_offset: Word,
317        output_index: Word,
318        transfer_amount: Word,
319        asset_id_offset: Word,
320    ) -> IoResult<(), S::Error>
321    where
322        Tx: ExecutableTransaction,
323        S: ContractsAssetsStorage,
324        V: Verifier,
325    {
326        let out_idx =
327            convert::to_usize(output_index).ok_or(PanicReason::OutputNotFound)?;
328        let to = Address::from(self.memory.read_bytes(recipient_offset)?);
329        let asset_id = AssetId::from(self.memory.read_bytes(asset_id_offset)?);
330        let amount = transfer_amount;
331
332        if amount == 0 {
333            return Err(PanicReason::TransferZeroCoins.into())
334        }
335
336        let internal_context = match internal_contract(self.context, self.fp, self.memory)
337        {
338            // optimistically attempt to load the internal contract id
339            Ok(source_contract) => Some(source_contract),
340            // revert to external context if no internal contract is set
341            Err(PanicReason::ExpectedInternalContext) => None,
342            // bubble up any other kind of errors
343            Err(e) => return Err(e.into()),
344        };
345
346        if let Some(source_contract) = internal_context {
347            // debit funding source (source contract balance)
348            balance_decrease(self.storage, &source_contract, &asset_id, amount)?;
349        } else {
350            // debit external funding source (i.e. UTXOs)
351            external_asset_id_balance_sub(self.balances, self.memory, &asset_id, amount)?;
352        }
353
354        // credit variable output
355        let variable = Output::variable(to, amount, asset_id);
356
357        set_variable_output(self.tx, self.memory, self.tx_offset, out_idx, variable)?;
358
359        let receipt = Receipt::transfer_out(
360            internal_context.unwrap_or_default(),
361            to,
362            amount,
363            asset_id,
364            *self.pc,
365            *self.is,
366        );
367
368        self.receipts.push(receipt)?;
369
370        inc_pc(self.pc);
371        Ok(())
372    }
373}
374
375pub(crate) fn contract_size<S>(
376    storage: &S,
377    contract: &ContractId,
378) -> IoResult<usize, S::Error>
379where
380    S: StorageSize<ContractsRawCode> + ?Sized,
381{
382    let size = storage
383        .size_of_value(contract)
384        .map_err(RuntimeError::Storage)?
385        .ok_or(PanicReason::ContractNotFound)?;
386    Ok(size)
387}
388
389pub(crate) fn blob_size<S>(storage: &S, blob_id: &BlobId) -> IoResult<usize, S::Error>
390where
391    S: StorageSize<BlobData> + ?Sized,
392{
393    let size = storage
394        .size_of_value(blob_id)
395        .map_err(RuntimeError::Storage)?
396        .ok_or(PanicReason::BlobNotFound)?;
397    Ok(size)
398}
399
400pub(crate) fn balance<S>(
401    storage: &S,
402    contract: &ContractId,
403    asset_id: &AssetId,
404) -> IoResult<Word, S::Error>
405where
406    S: ContractsAssetsStorage + ?Sized,
407{
408    Ok(storage
409        .contract_asset_id_balance(contract, asset_id)
410        .map_err(RuntimeError::Storage)?
411        .unwrap_or_default())
412}
413
414/// Increase the asset balance for a contract, unless the `amount` is zero.
415/// A boolean indicating if a new entry was created.
416pub fn balance_increase<S>(
417    storage: &mut S,
418    contract: &ContractId,
419    asset_id: &AssetId,
420    amount: Word,
421) -> IoResult<bool, S::Error>
422where
423    S: ContractsAssetsStorage + ?Sized,
424{
425    if amount == 0 {
426        // Don't update the balance if the amount is zero
427        return Ok(false)
428    }
429
430    let balance = balance(storage, contract, asset_id)?;
431    let balance = balance
432        .checked_add(amount)
433        .ok_or(PanicReason::BalanceOverflow)?;
434    let old_value = storage
435        .contract_asset_id_balance_replace(contract, asset_id, balance)
436        .map_err(RuntimeError::Storage)?;
437    Ok(old_value.is_none())
438}
439
440/// Decrease the asset balance for a contract, unless the `amount` is zero.
441pub fn balance_decrease<S>(
442    storage: &mut S,
443    contract: &ContractId,
444    asset_id: &AssetId,
445    amount: Word,
446) -> IoResult<(), S::Error>
447where
448    S: ContractsAssetsStorage + ?Sized,
449{
450    if amount == 0 {
451        // Don't update the balance if the amount is zero
452        return Ok(())
453    }
454
455    let balance = balance(storage, contract, asset_id)?;
456    let balance = balance
457        .checked_sub(amount)
458        .ok_or(PanicReason::NotEnoughBalance)?;
459    storage
460        .contract_asset_id_balance_insert(contract, asset_id, balance)
461        .map_err(RuntimeError::Storage)?;
462    Ok(())
463}