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