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
32mod 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 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 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 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 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 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 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#[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}