fuel_vm/interpreter/
contract.rs

1//! This module contains logic on contract management.
2
3use alloc::collections::BTreeSet;
4
5use super::{
6    gas::gas_charge,
7    internal::{
8        external_asset_id_balance_sub,
9        inc_pc,
10        internal_contract,
11        set_variable_output,
12    },
13    ExecutableTransaction,
14    Interpreter,
15    Memory,
16    MemoryInstance,
17    PanicContext,
18    RuntimeBalances,
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        Ok(inc_pc(self.pc)?)
208    }
209}
210struct TransferCtx<'vm, S, Tx, V> {
211    storage: &'vm mut S,
212    memory: &'vm mut MemoryInstance,
213    context: &'vm Context,
214    balances: &'vm mut RuntimeBalances,
215    receipts: &'vm mut ReceiptsCtx,
216
217    new_storage_gas_per_byte: Word,
218    tx: &'vm mut Tx,
219    input_contracts: &'vm BTreeSet<ContractId>,
220    panic_context: &'vm mut PanicContext,
221    tx_offset: usize,
222    cgas: RegMut<'vm, CGAS>,
223    ggas: RegMut<'vm, GGAS>,
224    fp: Reg<'vm, FP>,
225    is: Reg<'vm, IS>,
226    pc: RegMut<'vm, PC>,
227    verifier: &'vm mut V,
228}
229
230impl<S, Tx, V> TransferCtx<'_, S, Tx, V> {
231    /// In Fuel specs:
232    /// Transfer $rB coins with asset ID at $rC to contract with ID at $rA.
233    /// $rA -> recipient_contract_id_offset
234    /// $rB -> transfer_amount
235    /// $rC -> asset_id_offset
236    pub(crate) fn transfer(
237        self,
238        recipient_contract_id_offset: Word,
239        transfer_amount: Word,
240        asset_id_offset: Word,
241    ) -> IoResult<(), S::Error>
242    where
243        Tx: ExecutableTransaction,
244        S: ContractsAssetsStorage,
245        V: Verifier,
246    {
247        let amount = transfer_amount;
248        let destination =
249            ContractId::from(self.memory.read_bytes(recipient_contract_id_offset)?);
250        let asset_id = AssetId::from(self.memory.read_bytes(asset_id_offset)?);
251
252        self.verifier.check_contract_in_inputs(
253            self.panic_context,
254            self.input_contracts,
255            &destination,
256        )?;
257
258        if amount == 0 {
259            return Err(PanicReason::TransferZeroCoins.into())
260        }
261
262        let internal_context = match internal_contract(self.context, self.fp, self.memory)
263        {
264            // optimistically attempt to load the internal contract id
265            Ok(source_contract) => Some(source_contract),
266            // revert to external context if no internal contract is set
267            Err(PanicReason::ExpectedInternalContext) => None,
268            // bubble up any other kind of errors
269            Err(e) => return Err(e.into()),
270        };
271
272        if let Some(source_contract) = internal_context {
273            // debit funding source (source contract balance)
274            balance_decrease(self.storage, &source_contract, &asset_id, amount)?;
275        } else {
276            // debit external funding source (i.e. free balance)
277            external_asset_id_balance_sub(self.balances, self.memory, &asset_id, amount)?;
278        }
279        // credit destination contract
280        let (_, created_new_entry) =
281            balance_increase(self.storage, &destination, &asset_id, amount)?;
282        if created_new_entry {
283            // If a new entry was created, we must charge gas for it
284            gas_charge(
285                self.cgas,
286                self.ggas,
287                ((Bytes32::LEN + WORD_SIZE) as u64)
288                    .saturating_mul(self.new_storage_gas_per_byte),
289            )?;
290        }
291
292        let receipt = Receipt::transfer(
293            internal_context.unwrap_or_default(),
294            destination,
295            amount,
296            asset_id,
297            *self.pc,
298            *self.is,
299        );
300
301        self.receipts.push(receipt)?;
302
303        Ok(inc_pc(self.pc)?)
304    }
305
306    /// In Fuel specs:
307    /// Transfer $rC coins with asset ID at $rD to address at $rA, with output $rB.
308    /// $rA -> recipient_offset
309    /// $rB -> output_index
310    /// $rC -> transfer_amount
311    /// $rD -> asset_id_offset
312    pub(crate) fn transfer_output(
313        self,
314        recipient_offset: Word,
315        output_index: Word,
316        transfer_amount: Word,
317        asset_id_offset: Word,
318    ) -> IoResult<(), S::Error>
319    where
320        Tx: ExecutableTransaction,
321        S: ContractsAssetsStorage,
322        V: Verifier,
323    {
324        let out_idx =
325            convert::to_usize(output_index).ok_or(PanicReason::OutputNotFound)?;
326        let to = Address::from(self.memory.read_bytes(recipient_offset)?);
327        let asset_id = AssetId::from(self.memory.read_bytes(asset_id_offset)?);
328        let amount = transfer_amount;
329
330        if amount == 0 {
331            return Err(PanicReason::TransferZeroCoins.into())
332        }
333
334        let internal_context = match internal_contract(self.context, self.fp, self.memory)
335        {
336            // optimistically attempt to load the internal contract id
337            Ok(source_contract) => Some(source_contract),
338            // revert to external context if no internal contract is set
339            Err(PanicReason::ExpectedInternalContext) => None,
340            // bubble up any other kind of errors
341            Err(e) => return Err(e.into()),
342        };
343
344        if let Some(source_contract) = internal_context {
345            // debit funding source (source contract balance)
346            balance_decrease(self.storage, &source_contract, &asset_id, amount)?;
347        } else {
348            // debit external funding source (i.e. UTXOs)
349            external_asset_id_balance_sub(self.balances, self.memory, &asset_id, amount)?;
350        }
351
352        // credit variable output
353        let variable = Output::variable(to, amount, asset_id);
354
355        set_variable_output(self.tx, self.memory, self.tx_offset, out_idx, variable)?;
356
357        let receipt = Receipt::transfer_out(
358            internal_context.unwrap_or_default(),
359            to,
360            amount,
361            asset_id,
362            *self.pc,
363            *self.is,
364        );
365
366        self.receipts.push(receipt)?;
367
368        Ok(inc_pc(self.pc)?)
369    }
370}
371
372pub(crate) fn contract_size<S>(
373    storage: &S,
374    contract: &ContractId,
375) -> IoResult<usize, S::Error>
376where
377    S: StorageSize<ContractsRawCode> + ?Sized,
378{
379    let size = storage
380        .size_of_value(contract)
381        .map_err(RuntimeError::Storage)?
382        .ok_or(PanicReason::ContractNotFound)?;
383    Ok(size)
384}
385
386pub(crate) fn blob_size<S>(storage: &S, blob_id: &BlobId) -> IoResult<usize, S::Error>
387where
388    S: StorageSize<BlobData> + ?Sized,
389{
390    let size = storage
391        .size_of_value(blob_id)
392        .map_err(RuntimeError::Storage)?
393        .ok_or(PanicReason::BlobNotFound)?;
394    Ok(size)
395}
396
397pub(crate) fn balance<S>(
398    storage: &S,
399    contract: &ContractId,
400    asset_id: &AssetId,
401) -> IoResult<Word, S::Error>
402where
403    S: ContractsAssetsStorage + ?Sized,
404{
405    Ok(storage
406        .contract_asset_id_balance(contract, asset_id)
407        .map_err(RuntimeError::Storage)?
408        .unwrap_or_default())
409}
410
411/// Increase the asset balance for a contract.
412/// Returns new balance, and a boolean indicating if a new entry was created.
413pub fn balance_increase<S>(
414    storage: &mut S,
415    contract: &ContractId,
416    asset_id: &AssetId,
417    amount: Word,
418) -> IoResult<(Word, bool), S::Error>
419where
420    S: ContractsAssetsStorage + ?Sized,
421{
422    let balance = balance(storage, contract, asset_id)?;
423    let balance = balance
424        .checked_add(amount)
425        .ok_or(PanicReason::BalanceOverflow)?;
426
427    let old_value = storage
428        .contract_asset_id_balance_replace(contract, asset_id, balance)
429        .map_err(RuntimeError::Storage)?;
430
431    Ok((balance, old_value.is_none()))
432}
433
434/// Decrease the asset balance for a contract.
435pub fn balance_decrease<S>(
436    storage: &mut S,
437    contract: &ContractId,
438    asset_id: &AssetId,
439    amount: Word,
440) -> IoResult<Word, S::Error>
441where
442    S: ContractsAssetsStorage + ?Sized,
443{
444    let balance = balance(storage, contract, asset_id)?;
445    let balance = balance
446        .checked_sub(amount)
447        .ok_or(PanicReason::NotEnoughBalance)?;
448    storage
449        .contract_asset_id_balance_insert(contract, asset_id, balance)
450        .map_err(RuntimeError::Storage)?;
451    Ok(balance)
452}