fuels_programs/contract/
loader.rs1use std::collections::HashSet;
2
3use fuel_tx::{Bytes32, ContractId, Salt, StorageSlot};
4use fuels_accounts::Account;
5use fuels_core::{
6 constants::WORD_SIZE,
7 types::{
8 bech32::Bech32ContractId,
9 errors::{error, Result},
10 transaction::TxPolicies,
11 transaction_builders::{Blob, BlobId, BlobTransactionBuilder, TransactionBuilder},
12 },
13};
14
15use crate::{assembly::contract_call::loader_contract_asm, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE};
16
17use super::{compute_contract_id_and_state_root, Contract, Regular};
18
19#[derive(Debug, Clone)]
20pub struct BlobsUploaded {
21 blob_ids: Vec<BlobId>,
22}
23
24#[derive(Debug, Clone)]
25pub struct BlobsNotUploaded {
26 blobs: Vec<Blob>,
27}
28
29#[derive(Debug, Clone)]
30pub struct Loader<Blobs> {
31 as_blobs: Blobs,
32}
33
34impl Contract<Loader<BlobsNotUploaded>> {
35 pub fn code(&self) -> Vec<u8> {
36 let ids: Vec<_> = self.blob_ids();
37 loader_contract_asm(&ids)
38 .expect("a contract to be creatable due to the check done in loader_from_blobs")
39 }
40
41 pub fn contract_id(&self) -> ContractId {
42 self.compute_roots().0
43 }
44
45 pub fn code_root(&self) -> Bytes32 {
46 self.compute_roots().1
47 }
48
49 pub fn state_root(&self) -> Bytes32 {
50 self.compute_roots().2
51 }
52
53 fn compute_roots(&self) -> (ContractId, Bytes32, Bytes32) {
54 compute_contract_id_and_state_root(&self.code(), &self.salt, &self.storage_slots)
55 }
56
57 pub fn loader_from_blobs(
64 blobs: Vec<Blob>,
65 salt: Salt,
66 storage_slots: Vec<StorageSlot>,
67 ) -> Result<Self> {
68 if blobs.is_empty() {
69 return Err(error!(Other, "must provide at least one blob"));
70 }
71
72 let idx_of_last_blob = blobs.len().saturating_sub(1);
73 let idx_of_offender = blobs.iter().enumerate().find_map(|(idx, blob)| {
74 (blob.len() % WORD_SIZE != 0 && idx != idx_of_last_blob).then_some(idx)
75 });
76
77 if let Some(idx) = idx_of_offender {
78 return Err(error!(
79 Other,
80 "blob {}/{} has a size of {} bytes, which is not a multiple of {WORD_SIZE}",
81 idx.saturating_add(1),
82 blobs.len(),
83 blobs[idx].len()
84 ));
85 }
86
87 let ids = blobs.iter().map(|blob| blob.id()).collect::<Vec<_>>();
88
89 loader_contract_asm(&ids)?;
91
92 Ok(Self {
93 code: Loader {
94 as_blobs: BlobsNotUploaded { blobs },
95 },
96 salt,
97 storage_slots,
98 })
99 }
100
101 pub fn blobs(&self) -> &[Blob] {
102 self.code.as_blobs.blobs.as_slice()
103 }
104
105 pub fn blob_ids(&self) -> Vec<BlobId> {
106 self.code
107 .as_blobs
108 .blobs
109 .iter()
110 .map(|blob| blob.id())
111 .collect()
112 }
113
114 pub async fn upload_blobs(
117 self,
118 account: &impl Account,
119 tx_policies: TxPolicies,
120 ) -> Result<Contract<Loader<BlobsUploaded>>> {
121 let provider = account.try_provider()?;
122
123 let all_blob_ids = self.blob_ids();
124 let mut already_uploaded = HashSet::new();
125
126 for blob in self.code.as_blobs.blobs {
127 let id = blob.id();
128
129 if already_uploaded.contains(&id) {
130 continue;
131 }
132
133 if provider.blob_exists(id).await? {
134 already_uploaded.insert(id);
135 continue;
136 }
137
138 let mut tb = BlobTransactionBuilder::default()
139 .with_blob(blob)
140 .with_tx_policies(tx_policies)
141 .with_max_fee_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE);
142
143 account.adjust_for_fee(&mut tb, 0).await?;
144 account.add_witnesses(&mut tb)?;
145
146 let tx = tb.build(provider).await?;
147
148 let tx_status_response = provider.send_transaction_and_await_commit(tx).await;
149 tx_status_response.and_then(|response| response.check(None))?;
150
151 already_uploaded.insert(id);
152 }
153
154 Contract::loader_from_blob_ids(all_blob_ids, self.salt, self.storage_slots)
155 }
156
157 pub async fn deploy(
159 self,
160 account: &impl Account,
161 tx_policies: TxPolicies,
162 ) -> Result<Bech32ContractId> {
163 self.upload_blobs(account, tx_policies)
164 .await?
165 .deploy(account, tx_policies)
166 .await
167 }
168
169 pub async fn deploy_if_not_exists(
172 self,
173 account: &impl Account,
174 tx_policies: TxPolicies,
175 ) -> Result<Bech32ContractId> {
176 self.upload_blobs(account, tx_policies)
177 .await?
178 .deploy_if_not_exists(account, tx_policies)
179 .await
180 }
181 pub fn revert_to_regular(self) -> Contract<Regular> {
183 let code = self
184 .code
185 .as_blobs
186 .blobs
187 .into_iter()
188 .flat_map(Vec::from)
189 .collect();
190
191 Contract::regular(code, self.salt, self.storage_slots)
192 }
193}
194
195impl Contract<Loader<BlobsUploaded>> {
196 pub fn code(&self) -> Vec<u8> {
197 loader_contract_asm(&self.code.as_blobs.blob_ids)
198 .expect("a contract to be creatable due to the check done in loader_for_blobs")
199 }
200
201 pub fn contract_id(&self) -> ContractId {
202 self.compute_roots().0
203 }
204
205 pub fn code_root(&self) -> Bytes32 {
206 self.compute_roots().1
207 }
208
209 pub fn state_root(&self) -> Bytes32 {
210 self.compute_roots().2
211 }
212
213 pub fn compute_roots(&self) -> (ContractId, Bytes32, Bytes32) {
214 compute_contract_id_and_state_root(&self.code(), &self.salt, &self.storage_slots)
215 }
216
217 pub fn loader_from_blob_ids(
222 blob_ids: Vec<BlobId>,
223 salt: Salt,
224 storage_slots: Vec<StorageSlot>,
225 ) -> Result<Self> {
226 if blob_ids.is_empty() {
227 return Err(error!(Other, "must provide at least one blob"));
228 }
229
230 loader_contract_asm(&blob_ids)?;
232
233 Ok(Self {
234 code: Loader {
235 as_blobs: BlobsUploaded { blob_ids },
236 },
237 salt,
238 storage_slots,
239 })
240 }
241
242 pub fn blob_ids(&self) -> &[BlobId] {
243 &self.code.as_blobs.blob_ids
244 }
245
246 pub async fn deploy(
248 self,
249 account: &impl Account,
250 tx_policies: TxPolicies,
251 ) -> Result<Bech32ContractId> {
252 Contract::regular(self.code(), self.salt, self.storage_slots)
253 .deploy(account, tx_policies)
254 .await
255 }
256
257 pub async fn deploy_if_not_exists(
258 self,
259 account: &impl Account,
260 tx_policies: TxPolicies,
261 ) -> Result<Bech32ContractId> {
262 Contract::regular(self.code(), self.salt, self.storage_slots)
263 .deploy_if_not_exists(account, tx_policies)
264 .await
265 }
266}