fuel_core_executor/refs/
contract.rs

1use core::fmt;
2use fuel_core_storage::{
3    Error as StorageError,
4    Mappable,
5    MerkleRoot,
6    StorageAsRef,
7    StorageInspect,
8    iter::{
9        IterableStore,
10        IteratorOverTable,
11    },
12    not_found,
13    tables::{
14        ContractsAssets,
15        ContractsLatestUtxo,
16        ContractsState,
17    },
18};
19use fuel_core_types::{
20    fuel_crypto::Hasher,
21    fuel_types::{
22        Bytes32,
23        ContractId,
24    },
25    services::executor::{
26        Error as ExecutorError,
27        Result as ExecutorResult,
28    },
29};
30#[cfg(feature = "std")]
31use std::borrow::Cow;
32
33#[cfg(not(feature = "std"))]
34use alloc::borrow::Cow;
35
36use crate::contract_state_hash::{
37    compute_balances_hash,
38    compute_state_hash,
39};
40
41/// The wrapper around `contract_id` to simplify work with `Contract` in the database.
42pub struct ContractRef<Database> {
43    database: Database,
44    contract_id: ContractId,
45}
46
47impl<Database> ContractRef<Database> {
48    pub fn new(database: Database, contract_id: ContractId) -> Self {
49        Self {
50            database,
51            contract_id,
52        }
53    }
54
55    pub fn contract_id(&self) -> &ContractId {
56        &self.contract_id
57    }
58
59    pub fn database(&self) -> &Database {
60        &self.database
61    }
62
63    pub fn database_mut(&mut self) -> &mut Database {
64        &mut self.database
65    }
66}
67
68impl<Database> ContractRef<Database>
69where
70    Database: StorageInspect<ContractsLatestUtxo>,
71    ExecutorError: From<Database::Error>,
72{
73    pub fn utxo(
74        &self,
75    ) -> Result<
76        Option<Cow<'_, <ContractsLatestUtxo as Mappable>::OwnedValue>>,
77        Database::Error,
78    > {
79        self.database.storage().get(&self.contract_id)
80    }
81}
82
83impl<Database> ContractRef<Database>
84where
85    Database: StorageInspect<ContractsLatestUtxo>,
86    ExecutorError: From<Database::Error>,
87{
88    pub fn validated_utxo(
89        &self,
90        utxo_validation: bool,
91    ) -> ExecutorResult<<ContractsLatestUtxo as Mappable>::OwnedValue> {
92        let maybe_utxo_id = self.utxo()?.map(|utxo| utxo.into_owned());
93        let expected_utxo_id = if utxo_validation {
94            maybe_utxo_id.ok_or(ExecutorError::ContractUtxoMissing(self.contract_id))?
95        } else {
96            maybe_utxo_id.unwrap_or_default()
97        };
98        Ok(expected_utxo_id)
99    }
100}
101
102impl<Database> ContractRef<Database>
103where
104    Database: IterableStore<Column = fuel_core_storage::column::Column>,
105{
106    pub fn balance_root(&self) -> Result<Bytes32, StorageError> {
107        Ok(compute_balances_hash(
108            &self
109                .database
110                .iter_all_by_prefix::<ContractsAssets, _>(Some(self.contract_id))
111                .map(|res| res.map(|(key, value)| (*key.asset_id(), Some(value))))
112                .collect::<Result<_, _>>()?,
113        ))
114    }
115}
116
117impl<Database> ContractRef<Database>
118where
119    Database: IterableStore<Column = fuel_core_storage::column::Column>,
120{
121    pub fn state_root(&self) -> Result<Bytes32, StorageError> {
122        Ok(compute_state_hash(
123            &self
124                .database
125                .iter_all_by_prefix::<ContractsState, _>(Some(self.contract_id))
126                .map(|res| res.map(|(key, value)| (*key.state_key(), Some(value.into()))))
127                .collect::<Result<_, _>>()?,
128        ))
129    }
130}
131
132pub trait ContractStorageTrait:
133    StorageInspect<ContractsLatestUtxo, Error = Self::InnerError>
134{
135    type InnerError: fmt::Debug + fmt::Display + Send + Sync + 'static;
136}
137
138impl<D, E> ContractStorageTrait for D
139where
140    D: StorageInspect<ContractsLatestUtxo, Error = E>,
141    E: fmt::Debug + fmt::Display + Send + Sync + 'static,
142{
143    type InnerError = E;
144}
145
146impl<'a, Database> ContractRef<&'a Database>
147where
148    &'a Database: ContractStorageTrait<InnerError = StorageError>,
149    &'a Database: IterableStore<Column = fuel_core_storage::column::Column>,
150{
151    /// Returns the state root of the whole contract.
152    pub fn root(&self) -> anyhow::Result<MerkleRoot> {
153        let contract_id = *self.contract_id();
154        let utxo = *self
155            .database()
156            .storage::<ContractsLatestUtxo>()
157            .get(&contract_id)?
158            .ok_or(not_found!(ContractsLatestUtxo))?
159            .into_owned()
160            .utxo_id();
161
162        let state_root = self.state_root()?;
163
164        let balance_root = self.balance_root()?;
165
166        let contract_hash = *Hasher::default()
167            // `ContractId` already is based on contract's code and salt so we don't need it.
168            .chain(contract_id.as_ref())
169            .chain(utxo.tx_id().as_ref())
170            .chain(utxo.output_index().to_be_bytes())
171            .chain(state_root.as_slice())
172            .chain(balance_root.as_slice())
173            .finalize();
174
175        Ok(contract_hash)
176    }
177}