fuel_vm/interpreter/
initialization.rs

1use super::{
2    ExecutableTransaction,
3    InitialBalances,
4    Interpreter,
5    Memory,
6    RuntimeBalances,
7};
8use crate::{
9    checked_transaction::{
10        IntoChecked,
11        Ready,
12    },
13    consts::*,
14    context::Context,
15    error::{
16        Bug,
17        BugVariant,
18        InterpreterError,
19    },
20    interpreter::CheckedMetadata,
21    prelude::RuntimeError,
22    storage::InterpreterStorage,
23};
24use fuel_asm::RegId;
25use fuel_tx::{
26    Input,
27    Output,
28    field::{
29        Owner,
30        Script,
31        ScriptGasLimit,
32    },
33};
34use fuel_types::{
35    Address,
36    Word,
37};
38
39impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
40where
41    M: Memory,
42    Tx: ExecutableTransaction,
43    S: InterpreterStorage,
44{
45    /// Initialize the VM with a given transaction
46    fn init_inner(
47        &mut self,
48        mut tx: Tx,
49        initial_balances: InitialBalances,
50        runtime_balances: RuntimeBalances,
51        gas_limit: Word,
52    ) -> Result<(), RuntimeError<S::DataError>> {
53        tx.prepare_sign();
54        self.tx = tx;
55        self.input_contracts = self
56            .tx
57            .inputs()
58            .iter()
59            .filter_map(|i| match i {
60                Input::Contract(contract) => Some(contract.contract_id),
61                _ => None,
62            })
63            .collect();
64
65        let mut owner: Option<(usize, Address)> = None;
66
67        // If policy is set, use the owner from it.
68        if let Some(owner_index) = self.tx.owner() {
69            // Error below shouldn't be possible, because this check is done by
70            // `Checked<Tx>`.
71            let owner_index = u32::try_from(owner_index).map_err(|_| {
72                RuntimeError::Bug(Bug::new(BugVariant::TransactionOwnerIndexOutOfBounds))
73            })? as usize;
74
75            if owner_index >= self.tx.inputs().len() {
76                return Err(Bug::new(BugVariant::TransactionOwnerIndexOutOfBounds).into());
77            }
78
79            let input = &self.tx.inputs()[owner_index];
80            if let Some(input_owner) = input.input_owner() {
81                owner = Some((owner_index, *input_owner));
82            } else {
83                return Err(Bug::new(BugVariant::TransactionOwnerInputHasNoOwner {
84                    index: owner_index,
85                })
86                .into());
87            }
88        } else {
89            // Otherwise, use the owner if all inputs have the same owner.
90            for (idx, input) in self.tx.inputs().iter().enumerate() {
91                if let Some(input_owner) = input.input_owner() {
92                    match owner {
93                        None => {
94                            owner = Some((idx, *input_owner));
95                        }
96                        Some((_, cached_owner)) => {
97                            if *input_owner != cached_owner {
98                                owner = None;
99                                break;
100                            }
101                        }
102                    }
103                }
104            }
105        }
106
107        let owner = owner.and_then(|(idx, _)| {
108            let tx_offset = self.tx_offset() as Word;
109            let owner_offset = self
110                .tx
111                .inputs()
112                .get(idx)
113                .map(Input::repr)
114                .and_then(|r| r.owner_offset())
115                .and_then(|ofs| {
116                    self.tx.inputs_offset_at(idx).map(|o| o.saturating_add(ofs))
117                })?;
118
119            let owner_ptr = tx_offset.saturating_add(owner_offset as Word);
120
121            Some(owner_ptr)
122        });
123
124        self.owner_ptr = owner;
125
126        self.input_contracts_index_to_output_index = self
127            .tx
128            .outputs()
129            .iter()
130            .enumerate()
131            .filter_map(|(output_idx, o)| match o {
132                Output::Contract(fuel_tx::output::contract::Contract {
133                    input_index,
134                    ..
135                }) => Some((
136                    *input_index,
137                    u16::try_from(output_idx)
138                        .expect("The maximum number of outputs is `u16::MAX`"),
139                )),
140                _ => None,
141            })
142            .collect();
143
144        self.initial_balances = initial_balances.clone();
145
146        self.frames.clear();
147        self.receipts.clear();
148        self.memory_mut().reset();
149
150        // Optimized for memset
151        self.registers.iter_mut().for_each(|r| *r = 0);
152
153        self.registers[RegId::ONE] = 1;
154
155        // Set heap area
156        self.registers[RegId::HP] = VM_MAX_RAM;
157
158        // Initialize stack
159        macro_rules! push_stack {
160            ($v:expr) => {{
161                let data = $v;
162                let old_ssp = self.registers[RegId::SSP];
163                let new_ssp = old_ssp
164                    .checked_add(data.len() as Word)
165                    .expect("VM initialization data must fit into the stack");
166                self.memory_mut().grow_stack(new_ssp)?;
167                self.registers[RegId::SSP] = new_ssp;
168                self.memory_mut()
169                    .write_noownerchecks(old_ssp, data.len())
170                    .expect("VM initialization data must fit into the stack")
171                    .copy_from_slice(data);
172            }};
173        }
174
175        push_stack!(&*self.transaction().id(&self.chain_id()));
176
177        let base_asset_id = self.interpreter_params.base_asset_id;
178        push_stack!(&*base_asset_id);
179
180        runtime_balances.to_vm(self);
181
182        let tx_size = self.transaction().size() as Word;
183        self.set_gas(gas_limit);
184
185        push_stack!(&tx_size.to_be_bytes());
186
187        let tx_bytes = self.tx.to_bytes();
188        push_stack!(tx_bytes.as_slice());
189
190        self.registers[RegId::SP] = self.registers[RegId::SSP];
191
192        Ok(())
193    }
194}
195
196impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
197where
198    M: Memory,
199    Tx: ExecutableTransaction,
200    S: InterpreterStorage,
201{
202    /// Initialize the VM for a predicate context
203    pub fn init_predicate(
204        &mut self,
205        context: Context,
206        tx: Tx,
207        gas_limit: Word,
208    ) -> Result<(), InterpreterError<S::DataError>> {
209        self.context = context;
210        let initial_balances: InitialBalances = Default::default();
211        let runtime_balances = initial_balances.clone().try_into()?;
212
213        let range = self
214            .context
215            .predicate()
216            .expect("The context is not predicate")
217            .program()
218            .words();
219
220        self.init_inner(tx, initial_balances, runtime_balances, gas_limit)?;
221        self.registers[RegId::PC] = range.start as fuel_asm::Word;
222        self.registers[RegId::IS] = range.start as fuel_asm::Word;
223
224        Ok(())
225    }
226}
227
228impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
229where
230    M: Memory,
231    S: InterpreterStorage,
232    <S as InterpreterStorage>::DataError: From<S::DataError>,
233    Tx: ExecutableTransaction,
234    <Tx as IntoChecked>::Metadata: CheckedMetadata,
235{
236    /// Initialize the VM with a given transaction, backed by a storage provider that
237    /// allows execution of contract opcodes.
238    ///
239    /// For predicate estimation and verification, check [`Self::init_predicate`]
240    pub fn init_script(
241        &mut self,
242        ready_tx: Ready<Tx>,
243    ) -> Result<(), InterpreterError<S::DataError>> {
244        let block_height = self.storage.block_height().map_err(RuntimeError::Storage)?;
245
246        self.context = Context::Script { block_height };
247
248        let (_, checked) = ready_tx.decompose();
249        let (tx, metadata): (Tx, Tx::Metadata) = checked.into();
250
251        let gas_limit = tx
252            .as_script()
253            .map(|script| *script.script_gas_limit())
254            .unwrap_or_default();
255
256        let initial_balances = metadata.balances();
257        let runtime_balances = initial_balances.try_into()?;
258        self.init_inner(tx, metadata.balances(), runtime_balances, gas_limit)?;
259
260        if let Some(script) = self.transaction().as_script() {
261            let offset = self.tx_offset().saturating_add(script.script_offset()) as Word;
262
263            debug_assert!(offset < VM_MAX_RAM);
264
265            self.registers[RegId::PC] = offset;
266            self.registers[RegId::IS] = offset;
267        }
268
269        Ok(())
270    }
271}