fuels_programs/contract/
regular.rs

1use std::{default::Default, fmt::Debug, path::Path};
2
3use fuel_tx::{Bytes32, ContractId, Salt, StorageSlot, TxId};
4use fuels_accounts::Account;
5use fuels_core::{
6    constants::WORD_SIZE,
7    error,
8    types::{
9        bech32::Bech32ContractId,
10        errors::Result,
11        transaction::{Transaction, TxPolicies},
12        transaction_builders::{Blob, CreateTransactionBuilder},
13        tx_status::Success,
14    },
15    Configurables,
16};
17
18use crate::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE;
19
20use super::{
21    compute_contract_id_and_state_root, validate_path_and_extension, BlobsNotUploaded, Contract,
22    Loader, StorageConfiguration,
23};
24
25#[derive(Clone, Debug)]
26pub struct DeployResponse {
27    pub tx_status: Option<Success>,
28    pub tx_id: Option<TxId>,
29    pub contract_id: Bech32ContractId,
30}
31
32// In a mod so that we eliminate the footgun of getting the private `code` field without applying
33// configurables
34mod code_types {
35    use fuels_core::Configurables;
36
37    #[derive(Debug, Clone, PartialEq)]
38    pub struct Regular {
39        code: Vec<u8>,
40        configurables: Configurables,
41    }
42
43    impl Regular {
44        pub(crate) fn new(code: Vec<u8>, configurables: Configurables) -> Self {
45            Self {
46                code,
47                configurables,
48            }
49        }
50
51        pub(crate) fn with_code(self, code: Vec<u8>) -> Self {
52            Self { code, ..self }
53        }
54
55        pub(crate) fn with_configurables(self, configurables: Configurables) -> Self {
56            Self {
57                configurables,
58                ..self
59            }
60        }
61
62        pub(crate) fn code(&self) -> Vec<u8> {
63            let mut code = self.code.clone();
64            self.configurables.update_constants_in(&mut code);
65            code
66        }
67    }
68}
69pub use code_types::*;
70
71impl Contract<Regular> {
72    pub fn with_code(self, code: Vec<u8>) -> Self {
73        Self {
74            code: self.code.with_code(code),
75            salt: self.salt,
76            storage_slots: self.storage_slots,
77        }
78    }
79
80    pub fn with_configurables(self, configurables: impl Into<Configurables>) -> Self {
81        Self {
82            code: self.code.with_configurables(configurables.into()),
83            ..self
84        }
85    }
86
87    pub fn code(&self) -> Vec<u8> {
88        self.code.code()
89    }
90
91    pub fn contract_id(&self) -> ContractId {
92        self.compute_roots().0
93    }
94
95    pub fn code_root(&self) -> Bytes32 {
96        self.compute_roots().1
97    }
98
99    pub fn state_root(&self) -> Bytes32 {
100        self.compute_roots().2
101    }
102
103    fn compute_roots(&self) -> (ContractId, Bytes32, Bytes32) {
104        compute_contract_id_and_state_root(&self.code(), &self.salt, &self.storage_slots)
105    }
106
107    /// Loads a contract from a binary file. Salt and storage slots are loaded as well, depending on the configuration provided.
108    pub fn load_from(
109        binary_filepath: impl AsRef<Path>,
110        config: LoadConfiguration,
111    ) -> Result<Contract<Regular>> {
112        let binary_filepath = binary_filepath.as_ref();
113        validate_path_and_extension(binary_filepath, "bin")?;
114
115        let binary = std::fs::read(binary_filepath).map_err(|e| {
116            std::io::Error::new(
117                e.kind(),
118                format!("failed to read binary: {binary_filepath:?}: {e}"),
119            )
120        })?;
121
122        let storage_slots = super::determine_storage_slots(config.storage, binary_filepath)?;
123
124        Ok(Contract {
125            code: Regular::new(binary, config.configurables),
126            salt: config.salt,
127            storage_slots,
128        })
129    }
130
131    /// Creates a regular contract with the given code, salt, and storage slots.
132    pub fn regular(
133        code: Vec<u8>,
134        salt: Salt,
135        storage_slots: Vec<StorageSlot>,
136    ) -> Contract<Regular> {
137        Contract {
138            code: Regular::new(code, Configurables::default()),
139            salt,
140            storage_slots,
141        }
142    }
143
144    /// Deploys a compiled contract to a running node.
145    /// To deploy a contract, you need an account with enough assets to pay for deployment.
146    /// This account will also receive the change.
147    pub async fn deploy(
148        self,
149        account: &impl Account,
150        tx_policies: TxPolicies,
151    ) -> Result<DeployResponse> {
152        let contract_id = self.contract_id();
153        let state_root = self.state_root();
154        let salt = self.salt;
155        let storage_slots = self.storage_slots;
156
157        let mut tb = CreateTransactionBuilder::prepare_contract_deployment(
158            self.code.code(),
159            contract_id,
160            state_root,
161            salt,
162            storage_slots.to_vec(),
163            tx_policies,
164        )
165        .with_max_fee_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE);
166
167        account.add_witnesses(&mut tb)?;
168        account.adjust_for_fee(&mut tb, 0).await?;
169
170        let provider = account.try_provider()?;
171        let consensus_parameters = provider.consensus_parameters().await?;
172
173        let tx = tb.build(provider).await?;
174        let tx_id = Some(tx.id(consensus_parameters.chain_id()));
175
176        let tx_status = provider.send_transaction_and_await_commit(tx).await?;
177
178        Ok(DeployResponse {
179            tx_status: Some(tx_status.take_success_checked(None)?),
180            tx_id,
181            contract_id: contract_id.into(),
182        })
183    }
184
185    /// Deploys a compiled contract to a running node if a contract with
186    /// the corresponding [`ContractId`] doesn't exist.
187    pub async fn deploy_if_not_exists(
188        self,
189        account: &impl Account,
190        tx_policies: TxPolicies,
191    ) -> Result<DeployResponse> {
192        let contract_id = Bech32ContractId::from(self.contract_id());
193        let provider = account.try_provider()?;
194        if provider.contract_exists(&contract_id).await? {
195            Ok(DeployResponse {
196                tx_status: None,
197                tx_id: None,
198                contract_id,
199            })
200        } else {
201            self.deploy(account, tx_policies).await
202        }
203    }
204
205    /// Converts a regular contract into a loader contract, splitting the code into blobs.
206    pub fn convert_to_loader(
207        self,
208        max_words_per_blob: usize,
209    ) -> Result<Contract<Loader<BlobsNotUploaded>>> {
210        if max_words_per_blob == 0 {
211            return Err(error!(Other, "blob size must be greater than 0"));
212        }
213        let blobs = self
214            .code()
215            .chunks(max_words_per_blob.saturating_mul(WORD_SIZE))
216            .map(|chunk| Blob::new(chunk.to_vec()))
217            .collect();
218
219        Contract::loader_from_blobs(blobs, self.salt, self.storage_slots)
220    }
221
222    /// Deploys the contract either as a regular contract or as a loader contract if it exceeds the size limit.
223    pub async fn smart_deploy(
224        self,
225        account: &impl Account,
226        tx_policies: TxPolicies,
227        max_words_per_blob: usize,
228    ) -> Result<DeployResponse> {
229        let provider = account.try_provider()?;
230        let max_contract_size = provider
231            .consensus_parameters()
232            .await?
233            .contract_params()
234            .contract_max_size() as usize;
235
236        if self.code().len() <= max_contract_size {
237            self.deploy(account, tx_policies).await
238        } else {
239            self.convert_to_loader(max_words_per_blob)?
240                .deploy(account, tx_policies)
241                .await
242        }
243    }
244}
245
246/// Configuration for contract deployment.
247#[derive(Debug, Clone, Default)]
248pub struct LoadConfiguration {
249    pub(crate) storage: StorageConfiguration,
250    pub(crate) configurables: Configurables,
251    pub(crate) salt: Salt,
252}
253
254impl LoadConfiguration {
255    pub fn new(
256        storage: StorageConfiguration,
257        configurables: impl Into<Configurables>,
258        salt: impl Into<Salt>,
259    ) -> Self {
260        Self {
261            storage,
262            configurables: configurables.into(),
263            salt: salt.into(),
264        }
265    }
266
267    pub fn with_storage_configuration(mut self, storage: StorageConfiguration) -> Self {
268        self.storage = storage;
269        self
270    }
271
272    pub fn with_configurables(mut self, configurables: impl Into<Configurables>) -> Self {
273        self.configurables = configurables.into();
274        self
275    }
276
277    pub fn with_salt(mut self, salt: impl Into<Salt>) -> Self {
278        self.salt = salt.into();
279        self
280    }
281}