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