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