fuels_programs/contract/
regular.rs

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