1use fuels_core::{
2 Configurables,
3 types::{
4 errors::{Context, Result},
5 transaction::Transaction,
6 transaction_builders::{Blob, BlobTransactionBuilder},
7 tx_response::TxResponse,
8 },
9};
10
11use crate::{
12 DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
13 assembly::script_and_predicate_loader::{
14 LoaderCode, extract_configurables_offset, extract_data_offset,
15 has_configurables_section_offset,
16 },
17};
18
19#[derive(Debug, Clone, PartialEq)]
21pub struct Regular {
22 code: Vec<u8>,
23 configurables: Configurables,
24}
25
26impl Regular {
27 pub fn new(code: Vec<u8>, configurables: Configurables) -> Self {
28 Self {
29 code,
30 configurables,
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
39pub struct Executable<State> {
40 state: State,
41}
42
43impl Executable<Regular> {
44 pub fn from_bytes(code: Vec<u8>) -> Self {
45 Executable {
46 state: Regular::new(code, Default::default()),
47 }
48 }
49
50 pub fn load_from(path: &str) -> Result<Executable<Regular>> {
60 let code = std::fs::read(path)?;
61
62 Ok(Executable {
63 state: Regular::new(code, Default::default()),
64 })
65 }
66
67 pub fn with_configurables(self, configurables: impl Into<Configurables>) -> Self {
68 Executable {
69 state: Regular {
70 configurables: configurables.into(),
71 ..self.state
72 },
73 }
74 }
75
76 pub fn data_offset_in_code(&self) -> Result<usize> {
77 extract_data_offset(&self.state.code)
78 }
79
80 pub fn configurables_offset_in_code(&self) -> Result<Option<usize>> {
81 if has_configurables_section_offset(&self.state.code)? {
82 Ok(Some(extract_configurables_offset(&self.state.code)?))
83 } else {
84 Ok(None)
85 }
86 }
87
88 pub fn code(&self) -> Vec<u8> {
94 let mut code = self.state.code.clone();
95 self.state.configurables.update_constants_in(&mut code);
96 code
97 }
98
99 pub fn convert_to_loader(self) -> Result<Executable<Loader>> {
106 validate_loader_can_be_made_from_code(
107 self.state.code.clone(),
108 self.state.configurables.clone(),
109 )?;
110
111 Ok(Executable {
112 state: Loader {
113 code: self.state.code,
114 configurables: self.state.configurables,
115 },
116 })
117 }
118}
119
120pub struct Loader {
121 code: Vec<u8>,
122 configurables: Configurables,
123}
124
125impl Executable<Loader> {
126 pub fn with_configurables(self, configurables: impl Into<Configurables>) -> Self {
127 Executable {
128 state: Loader {
129 configurables: configurables.into(),
130 ..self.state
131 },
132 }
133 }
134
135 #[deprecated(note = "Use `configurables_offset_in_code` instead")]
136 pub fn data_offset_in_code(&self) -> usize {
137 self.loader_code().configurables_section_offset()
138 }
139
140 pub fn configurables_offset_in_code(&self) -> usize {
141 self.loader_code().configurables_section_offset()
142 }
143
144 fn loader_code(&self) -> LoaderCode {
145 let mut code = self.state.code.clone();
146
147 self.state.configurables.update_constants_in(&mut code);
148
149 LoaderCode::from_normal_binary(code)
150 .expect("checked before turning into a Executable<Loader>")
151 }
152
153 pub fn code(&self) -> Vec<u8> {
155 self.loader_code().as_bytes().to_vec()
156 }
157
158 pub fn blob(&self) -> Blob {
160 LoaderCode::extract_blob(&self.state.code)
163 .expect("checked before turning into a Executable<Loader>")
164 }
165
166 pub async fn upload_blob(
168 &self,
169 account: impl fuels_accounts::Account,
170 ) -> Result<Option<TxResponse>> {
171 let blob = self.blob();
172 let provider = account.try_provider()?;
173 let consensus_parameters = provider.consensus_parameters().await?;
174
175 if provider.blob_exists(blob.id()).await? {
176 return Ok(None);
177 }
178
179 let mut tb = BlobTransactionBuilder::default()
180 .with_blob(self.blob())
181 .with_max_fee_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE);
182
183 account
184 .adjust_for_fee(&mut tb, 0)
185 .await
186 .context("failed to adjust inputs to cover for missing base asset")?;
187 account.add_witnesses(&mut tb)?;
188
189 let tx = tb.build(provider).await?;
190 let tx_id = tx.id(consensus_parameters.chain_id());
191
192 let tx_status = provider.send_transaction_and_await_commit(tx).await?;
193
194 Ok(Some(TxResponse {
195 tx_status: tx_status.take_success_checked(None)?,
196 tx_id,
197 }))
198 }
199}
200
201fn validate_loader_can_be_made_from_code(
202 mut code: Vec<u8>,
203 configurables: Configurables,
204) -> Result<()> {
205 configurables.update_constants_in(&mut code);
206
207 let _ = LoaderCode::from_normal_binary(code)?;
208
209 Ok(())
210}
211
212#[cfg(test)]
213mod tests {
214 use std::io::Write;
215
216 use fuels_core::{Configurable, Configurables};
217 use tempfile::NamedTempFile;
218
219 use super::*;
220
221 fn legacy_indicating_instruction() -> Vec<u8> {
222 fuel_asm::op::jmpf(0x0, 0x02).to_bytes().to_vec()
223 }
224
225 #[test]
226 fn test_executable_regular_from_bytes() {
227 let code = vec![1u8, 2, 3, 4];
229
230 let executable = Executable::<Regular>::from_bytes(code.clone());
232
233 assert_eq!(executable.state.code, code);
235 assert_eq!(executable.state.configurables, Default::default());
236 }
237
238 #[test]
239 fn test_executable_regular_load_from() {
240 let code = vec![5u8, 6, 7, 8];
242 let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
243 temp_file
244 .write_all(&code)
245 .expect("Failed to write to temp file");
246 let path = temp_file.path().to_str().unwrap();
247
248 let executable_result = Executable::<Regular>::load_from(path);
250
251 assert!(executable_result.is_ok());
253 let executable = executable_result.unwrap();
254 assert_eq!(executable.state.code, code);
255 assert_eq!(executable.state.configurables, Default::default());
256 }
257
258 #[test]
259 fn test_executable_regular_load_from_invalid_path() {
260 let invalid_path = "/nonexistent/path/to/file";
262
263 let executable_result = Executable::<Regular>::load_from(invalid_path);
265
266 assert!(executable_result.is_err());
268 }
269
270 #[test]
271 fn test_executable_regular_with_configurables() {
272 let code = vec![1u8, 2, 3, 4];
274 let executable = Executable::<Regular>::from_bytes(code);
275 let configurables = Configurables::new(vec![Configurable {
276 offset: 2,
277 data: vec![1],
278 }]);
279
280 let new_executable = executable.with_configurables(configurables.clone());
282
283 assert_eq!(new_executable.state.configurables, configurables);
285 }
286
287 #[test]
288 fn test_executable_regular_code() {
289 let code = vec![1u8, 2, 3, 4];
291 let configurables = Configurables::new(vec![Configurable {
292 offset: 1,
293 data: vec![1],
294 }]);
295 let executable =
296 Executable::<Regular>::from_bytes(code.clone()).with_configurables(configurables);
297
298 let modified_code = executable.code();
300
301 assert_eq!(modified_code, vec![1, 1, 3, 4]);
302 }
303
304 #[test]
305 fn test_loader_extracts_code_and_data_section_legacy_format() {
306 let padding = vec![0; 4];
307 let jmpf = legacy_indicating_instruction();
308 let data_offset = 28u64.to_be_bytes().to_vec();
309 let remaining_padding = vec![0; 8];
310 let some_random_instruction = vec![1, 2, 3, 4];
311 let data_section = vec![5, 6, 7, 8];
312
313 let code = [
314 padding.clone(),
315 jmpf.clone(),
316 data_offset.clone(),
317 remaining_padding.clone(),
318 some_random_instruction.clone(),
319 data_section.clone(),
320 ]
321 .concat();
322
323 let executable = Executable::<Regular>::from_bytes(code.clone());
324
325 let loader = executable.convert_to_loader().unwrap();
326
327 let blob = loader.blob();
328 let data_stripped_code = [
329 padding,
330 jmpf.clone(),
331 data_offset,
332 remaining_padding.clone(),
333 some_random_instruction,
334 ]
335 .concat();
336 assert_eq!(blob.as_ref(), data_stripped_code);
337
338 let loader_code = loader.code();
340
341 assert_eq!(
342 loader_code,
343 LoaderCode::from_normal_binary(code).unwrap().as_bytes()
344 );
345 }
346
347 #[test]
348 fn test_loader_extracts_code_and_configurable_section_new_format() {
349 let padding = vec![0; 4];
350 let jmpf = legacy_indicating_instruction();
351 let data_offset = 28u64.to_be_bytes().to_vec();
352 let configurable_offset = vec![0; 8];
353 let data_section = vec![5, 6, 7, 8];
354 let configurable_section = vec![9, 9, 9, 9];
355
356 let code = [
357 padding.clone(),
358 jmpf.clone(),
359 data_offset.clone(),
360 configurable_offset.clone(),
361 data_section.clone(),
362 configurable_section,
363 ]
364 .concat();
365
366 let executable = Executable::<Regular>::from_bytes(code.clone());
367
368 let loader = executable.convert_to_loader().unwrap();
369
370 let blob = loader.blob();
371 let configurable_stripped_code = [
372 padding,
373 jmpf,
374 data_offset,
375 configurable_offset,
376 data_section,
377 ]
378 .concat();
379 assert_eq!(blob.as_ref(), configurable_stripped_code);
380
381 let loader_code = loader.code();
383 assert_eq!(
384 loader_code,
385 LoaderCode::from_normal_binary(code).unwrap().as_bytes()
386 );
387 }
388
389 #[test]
390 fn test_executable_regular_convert_to_loader_with_invalid_code() {
391 let code = vec![1u8, 2]; let executable = Executable::<Regular>::from_bytes(code);
394
395 let result = executable.convert_to_loader();
397
398 assert!(result.is_err());
400 }
401
402 #[test]
403 fn executable_with_no_data_section() {
404 let data_section_offset = 16u64;
407
408 let jmpf = legacy_indicating_instruction();
409 let mut initial_bytes = vec![0; 16];
410 initial_bytes[4..8].copy_from_slice(&jmpf);
411
412 let code = [initial_bytes, data_section_offset.to_be_bytes().to_vec()].concat();
413
414 Executable::from_bytes(code).convert_to_loader().unwrap();
415 }
416}