fuel_vm/interpreter/
contract.rs

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