Skip to main content

fuel_vm/storage/
interpreter.rs

1//! Trait definitions for storage backend
2
3use fuel_storage::{
4    StorageAsRef,
5    StorageInspect,
6    StorageMutate,
7    StorageRead,
8    StorageSize,
9    StorageWrite,
10};
11use fuel_tx::{
12    ConsensusParameters,
13    Contract,
14    StorageSlot,
15};
16use fuel_types::{
17    AssetId,
18    BlockHeight,
19    Bytes32,
20    ContractId,
21    Word,
22};
23
24use crate::{
25    prelude::{
26        InterpreterError,
27        RuntimeError,
28    },
29    storage::{
30        ContractsAssets,
31        ContractsRawCode,
32        ContractsState,
33        ContractsStateData,
34        UploadedBytecode,
35        UploadedBytecodes,
36    },
37};
38use alloc::borrow::Cow;
39use core::ops::{
40    Deref,
41    DerefMut,
42};
43
44use super::blob_data::BlobData;
45
46/// When this trait is implemented, the underlying interpreter is guaranteed to
47/// have full functionality
48pub trait InterpreterStorage:
49    StorageWrite<ContractsRawCode, Error = Self::DataError>
50    + StorageSize<ContractsRawCode, Error = Self::DataError>
51    + StorageRead<ContractsRawCode, Error = Self::DataError>
52    + StorageWrite<ContractsState, Error = Self::DataError>
53    + StorageSize<ContractsState, Error = Self::DataError>
54    + StorageRead<ContractsState, Error = Self::DataError>
55    + StorageMutate<UploadedBytecodes, Error = Self::DataError>
56    + StorageWrite<BlobData, Error = Self::DataError>
57    + StorageSize<BlobData, Error = Self::DataError>
58    + StorageRead<BlobData, Error = Self::DataError>
59    + ContractsAssetsStorage<Error = Self::DataError>
60{
61    /// Error implementation for reasons unspecified in the protocol.
62    type DataError: Into<InterpreterError<Self::DataError>>
63        + Into<RuntimeError<Self::DataError>>
64        + core::fmt::Debug;
65
66    /// Provide the current block height in which the transactions should be
67    /// executed.
68    fn block_height(&self) -> Result<BlockHeight, Self::DataError>;
69
70    /// Provide the current version of consensus parameters used to execute transaction.
71    fn consensus_parameters_version(&self) -> Result<u32, Self::DataError>;
72
73    /// Provide the current version of state transition function used to execute
74    /// transaction.
75    fn state_transition_version(&self) -> Result<u32, Self::DataError>;
76
77    /// Return the timestamp of a given block
78    ///
79    /// This isn't optional because the VM is expected to panic if an invalid block height
80    /// is passed - under the assumption that the block height is consistent, the
81    /// storage should necessarily have the timestamp for the block, unless some I/O
82    /// error prevents it from fetching it.
83    fn timestamp(&self, height: BlockHeight) -> Result<Word, Self::DataError>;
84
85    /// Provide the block hash from a given height.
86    fn block_hash(&self, block_height: BlockHeight) -> Result<Bytes32, Self::DataError>;
87
88    /// Provide the coinbase address for the VM instructions implementation.
89    fn coinbase(&self) -> Result<ContractId, Self::DataError>;
90
91    /// Set the consensus parameters in the storage under the `version`.
92    ///
93    /// Returns the previous consensus parameters if they were set.
94    fn set_consensus_parameters(
95        &mut self,
96        version: u32,
97        consensus_parameters: &ConsensusParameters,
98    ) -> Result<Option<ConsensusParameters>, Self::DataError>;
99
100    /// Returns `true` if the fully uploaded state transition bytecode is present in the
101    /// storage.
102    fn contains_state_transition_bytecode_root(
103        &self,
104        root: &Bytes32,
105    ) -> Result<bool, Self::DataError> {
106        let bytecode = self.storage::<UploadedBytecodes>().get(root)?;
107
108        if let Some(cow) = bytecode {
109            if let UploadedBytecode::Completed(_) = cow.as_ref() {
110                Ok(true)
111            } else {
112                Ok(false)
113            }
114        } else {
115            Ok(false)
116        }
117    }
118
119    /// Set the state transition bytecode in the storage under the `version`.
120    ///
121    /// Returns the previous bytecode if it was set.
122    fn set_state_transition_bytecode(
123        &mut self,
124        version: u32,
125        hash: &Bytes32,
126    ) -> Result<Option<Bytes32>, Self::DataError>;
127
128    /// Deploy a contract into the storage with contract id
129    fn deploy_contract_with_id(
130        &mut self,
131        slots: &[StorageSlot],
132        contract: &[u8],
133        id: &ContractId,
134    ) -> Result<(), Self::DataError> {
135        self.storage_contract_insert(id, contract)?;
136
137        // On the `fuel-core` side it is done in more optimal way
138        slots.iter().try_for_each(|s| {
139            self.contract_state_insert(id, s.key(), s.value().as_ref())?;
140            Ok(())
141        })?;
142        Ok(())
143    }
144
145    /// Fetch a previously inserted contract code from the chain state for a
146    /// given contract.
147    fn storage_contract(
148        &self,
149        id: &ContractId,
150    ) -> Result<Option<Cow<'_, Contract>>, Self::DataError> {
151        StorageInspect::<ContractsRawCode>::get(self, id)
152    }
153
154    /// Fetch the size of a previously inserted contract code from the chain state for a
155    /// given contract.
156    fn storage_contract_size(
157        &self,
158        id: &ContractId,
159    ) -> Result<Option<usize>, Self::DataError> {
160        StorageSize::<ContractsRawCode>::size_of_value(self, id)
161    }
162
163    /// Append a contract to the chain, provided its identifier.
164    ///
165    /// Canonically, the identifier should be [`Contract::id`].
166    fn storage_contract_insert(
167        &mut self,
168        id: &ContractId,
169        contract: &[u8],
170    ) -> Result<(), Self::DataError> {
171        StorageMutate::<ContractsRawCode>::insert(self, id, contract)
172    }
173
174    /// Check if a provided contract exists in the chain.
175    fn storage_contract_exists(&self, id: &ContractId) -> Result<bool, Self::DataError> {
176        self.storage::<ContractsRawCode>().contains_key(id)
177    }
178
179    /// Fetch the value form a key-value mapping in a contract storage.
180    fn contract_state(
181        &self,
182        id: &ContractId,
183        key: &Bytes32,
184    ) -> Result<Option<Cow<'_, ContractsStateData>>, Self::DataError> {
185        StorageInspect::<ContractsState>::get(self, &(id, key).into())
186    }
187
188    /// Insert a key-value mapping in a contract storage.
189    fn contract_state_insert(
190        &mut self,
191        contract: &ContractId,
192        key: &Bytes32,
193        value: &[u8],
194    ) -> Result<(), Self::DataError> {
195        StorageWrite::<ContractsState>::write_bytes(
196            self,
197            &(contract, key).into(),
198            value,
199        )?;
200        Ok(())
201    }
202
203    /// Remove a range of key-values from contract storage.
204    fn contract_state_remove_range(
205        &mut self,
206        contract: &ContractId,
207        start_key: &Bytes32,
208        range: usize,
209    ) -> Result<(), Self::DataError>;
210}
211
212/// Storage operations for contract assets.
213pub trait ContractsAssetsStorage: StorageMutate<ContractsAssets> {
214    /// Fetch the balance of an asset ID in a contract storage.
215    fn contract_asset_id_balance(
216        &self,
217        id: &ContractId,
218        asset_id: &AssetId,
219    ) -> Result<Option<Word>, Self::Error> {
220        let balance = self
221            .storage::<ContractsAssets>()
222            .get(&(id, asset_id).into())?
223            .map(Cow::into_owned);
224
225        Ok(balance)
226    }
227
228    /// Update the balance of an asset ID in a contract storage.
229    fn contract_asset_id_balance_insert(
230        &mut self,
231        contract: &ContractId,
232        asset_id: &AssetId,
233        value: Word,
234    ) -> Result<(), Self::Error> {
235        StorageMutate::<ContractsAssets>::insert(
236            self,
237            &(contract, asset_id).into(),
238            &value,
239        )
240    }
241
242    /// Update the balance of an asset ID in a contract storage.
243    /// Returns the old balance, if any.
244    fn contract_asset_id_balance_replace(
245        &mut self,
246        contract: &ContractId,
247        asset_id: &AssetId,
248        value: Word,
249    ) -> Result<Option<Word>, Self::Error> {
250        StorageMutate::<ContractsAssets>::replace(
251            self,
252            &(contract, asset_id).into(),
253            &value,
254        )
255    }
256
257    /// Remove the balance of an asset ID in a contract storage.
258    fn contract_asset_id_balance_remove(
259        &mut self,
260        contract: &ContractId,
261        asset_id: &AssetId,
262    ) -> Result<(), Self::Error> {
263        StorageMutate::<ContractsAssets>::remove(self, &(contract, asset_id).into())
264    }
265}
266
267impl<S> ContractsAssetsStorage for &mut S where S: ContractsAssetsStorage {}
268
269impl<S> InterpreterStorage for &mut S
270where
271    S: InterpreterStorage,
272{
273    type DataError = <S as InterpreterStorage>::DataError;
274
275    fn block_height(&self) -> Result<BlockHeight, Self::DataError> {
276        <S as InterpreterStorage>::block_height(self.deref())
277    }
278
279    fn consensus_parameters_version(&self) -> Result<u32, Self::DataError> {
280        <S as InterpreterStorage>::consensus_parameters_version(self.deref())
281    }
282
283    fn state_transition_version(&self) -> Result<u32, Self::DataError> {
284        <S as InterpreterStorage>::state_transition_version(self.deref())
285    }
286
287    fn timestamp(&self, height: BlockHeight) -> Result<Word, Self::DataError> {
288        <S as InterpreterStorage>::timestamp(self.deref(), height)
289    }
290
291    fn block_hash(&self, block_height: BlockHeight) -> Result<Bytes32, Self::DataError> {
292        <S as InterpreterStorage>::block_hash(self.deref(), block_height)
293    }
294
295    fn coinbase(&self) -> Result<ContractId, Self::DataError> {
296        <S as InterpreterStorage>::coinbase(self.deref())
297    }
298
299    fn set_consensus_parameters(
300        &mut self,
301        version: u32,
302        consensus_parameters: &ConsensusParameters,
303    ) -> Result<Option<ConsensusParameters>, Self::DataError> {
304        <S as InterpreterStorage>::set_consensus_parameters(
305            self.deref_mut(),
306            version,
307            consensus_parameters,
308        )
309    }
310
311    fn set_state_transition_bytecode(
312        &mut self,
313        version: u32,
314        hash: &Bytes32,
315    ) -> Result<Option<Bytes32>, Self::DataError> {
316        <S as InterpreterStorage>::set_state_transition_bytecode(
317            self.deref_mut(),
318            version,
319            hash,
320        )
321    }
322
323    fn storage_contract_size(
324        &self,
325        id: &ContractId,
326    ) -> Result<Option<usize>, Self::DataError> {
327        <S as InterpreterStorage>::storage_contract_size(self.deref(), id)
328    }
329
330    fn contract_state_remove_range(
331        &mut self,
332        contract: &ContractId,
333        start_key: &Bytes32,
334        range: usize,
335    ) -> Result<(), Self::DataError> {
336        <S as InterpreterStorage>::contract_state_remove_range(
337            self.deref_mut(),
338            contract,
339            start_key,
340            range,
341        )
342    }
343}