fuels_programs/contract/
regular.rs

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