fuels_programs/
executable.rs

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/// This struct represents a standard executable with its associated bytecode and configurables.
20#[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/// Used to transform Script or Predicate code into a loader variant, where the code is uploaded as
36/// a blob and the binary itself is substituted with code that will load the blob code and apply
37/// the given configurables to the Script/Predicate.
38#[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    /// Loads an `Executable<Regular>` from a file at the given path.
51    ///
52    /// # Parameters
53    ///
54    /// - `path`: The file path to load the executable from.
55    ///
56    /// # Returns
57    ///
58    /// A `Result` containing the `Executable<Regular>` or an error if loading fails.
59    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    /// Returns the code of the executable with configurables applied.
89    ///
90    /// # Returns
91    ///
92    /// The bytecode of the executable with configurables updated.
93    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    /// Converts the `Executable<Regular>` into an `Executable<Loader>`.
100    ///
101    /// # Returns
102    ///
103    /// A `Result` containing the `Executable<Loader>` or an error if loader code cannot be
104    /// generated for the given binary.
105    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    /// Returns the code of the loader executable with configurables applied.
154    pub fn code(&self) -> Vec<u8> {
155        self.loader_code().as_bytes().to_vec()
156    }
157
158    /// A Blob containing the original executable code minus the data section.
159    pub fn blob(&self) -> Blob {
160        // we don't apply configurables because they touch the data section which isn't part of the
161        // blob
162        LoaderCode::extract_blob(&self.state.code)
163            .expect("checked before turning into a Executable<Loader>")
164    }
165
166    /// If not previously uploaded, uploads a blob containing the original executable code minus the data section.
167    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::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        // Given: Some bytecode
228        let code = vec![1u8, 2, 3, 4];
229
230        // When: Creating an Executable<Regular> from bytes
231        let executable = Executable::<Regular>::from_bytes(code.clone());
232
233        // Then: The executable should have the given code and default configurables
234        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        // Given: A temporary file containing some bytecode
241        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        // When: Loading an Executable<Regular> from the file
249        let executable_result = Executable::<Regular>::load_from(path);
250
251        // Then: The executable should be created successfully with the correct code
252        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        // Given: An invalid file path
261        let invalid_path = "/nonexistent/path/to/file";
262
263        // When: Attempting to load an Executable<Regular> from the invalid path
264        let executable_result = Executable::<Regular>::load_from(invalid_path);
265
266        // Then: The operation should fail with an error
267        assert!(executable_result.is_err());
268    }
269
270    #[test]
271    fn test_executable_regular_with_configurables() {
272        // Given: An Executable<Regular> and some configurables
273        let code = vec![1u8, 2, 3, 4];
274        let executable = Executable::<Regular>::from_bytes(code);
275        let configurables = Configurables::new(vec![(2, vec![1])]);
276
277        // When: Setting new configurables
278        let new_executable = executable.with_configurables(configurables.clone());
279
280        // Then: The executable should have the new configurables
281        assert_eq!(new_executable.state.configurables, configurables);
282    }
283
284    #[test]
285    fn test_executable_regular_code() {
286        // Given: An Executable<Regular> with some code and configurables
287        let code = vec![1u8, 2, 3, 4];
288        let configurables = Configurables::new(vec![(1, vec![1])]);
289        let executable =
290            Executable::<Regular>::from_bytes(code.clone()).with_configurables(configurables);
291
292        // When: Retrieving the code after applying configurables
293        let modified_code = executable.code();
294
295        assert_eq!(modified_code, vec![1, 1, 3, 4]);
296    }
297
298    #[test]
299    fn test_loader_extracts_code_and_data_section_legacy_format() {
300        let padding = vec![0; 4];
301        let jmpf = legacy_indicating_instruction();
302        let data_offset = 28u64.to_be_bytes().to_vec();
303        let remaining_padding = vec![0; 8];
304        let some_random_instruction = vec![1, 2, 3, 4];
305        let data_section = vec![5, 6, 7, 8];
306
307        let code = [
308            padding.clone(),
309            jmpf.clone(),
310            data_offset.clone(),
311            remaining_padding.clone(),
312            some_random_instruction.clone(),
313            data_section.clone(),
314        ]
315        .concat();
316
317        let executable = Executable::<Regular>::from_bytes(code.clone());
318
319        let loader = executable.convert_to_loader().unwrap();
320
321        let blob = loader.blob();
322        let data_stripped_code = [
323            padding,
324            jmpf.clone(),
325            data_offset,
326            remaining_padding.clone(),
327            some_random_instruction,
328        ]
329        .concat();
330        assert_eq!(blob.as_ref(), data_stripped_code);
331
332        // And: Loader code should match expected binary
333        let loader_code = loader.code();
334
335        assert_eq!(
336            loader_code,
337            LoaderCode::from_normal_binary(code).unwrap().as_bytes()
338        );
339    }
340
341    #[test]
342    fn test_loader_extracts_code_and_configurable_section_new_format() {
343        let padding = vec![0; 4];
344        let jmpf = legacy_indicating_instruction();
345        let data_offset = 28u64.to_be_bytes().to_vec();
346        let configurable_offset = vec![0; 8];
347        let data_section = vec![5, 6, 7, 8];
348        let configurable_section = vec![9, 9, 9, 9];
349
350        let code = [
351            padding.clone(),
352            jmpf.clone(),
353            data_offset.clone(),
354            configurable_offset.clone(),
355            data_section.clone(),
356            configurable_section,
357        ]
358        .concat();
359
360        let executable = Executable::<Regular>::from_bytes(code.clone());
361
362        let loader = executable.convert_to_loader().unwrap();
363
364        let blob = loader.blob();
365        let configurable_stripped_code = [
366            padding,
367            jmpf,
368            data_offset,
369            configurable_offset,
370            data_section,
371        ]
372        .concat();
373        assert_eq!(blob.as_ref(), configurable_stripped_code);
374
375        // And: Loader code should match expected binary
376        let loader_code = loader.code();
377        assert_eq!(
378            loader_code,
379            LoaderCode::from_normal_binary(code).unwrap().as_bytes()
380        );
381    }
382
383    #[test]
384    fn test_executable_regular_convert_to_loader_with_invalid_code() {
385        // Given: An Executable<Regular> with invalid code (too short)
386        let code = vec![1u8, 2]; // Insufficient length for a valid data offset
387        let executable = Executable::<Regular>::from_bytes(code);
388
389        // When: Attempting to convert to a loader
390        let result = executable.convert_to_loader();
391
392        // Then: The conversion should fail with an error
393        assert!(result.is_err());
394    }
395
396    #[test]
397    fn executable_with_no_data_section() {
398        // to skip over the first 2 half words and skip over the offset itself, basically stating
399        // that there is no data section
400        let data_section_offset = 16u64;
401
402        let jmpf = legacy_indicating_instruction();
403        let mut initial_bytes = vec![0; 16];
404        initial_bytes[4..8].copy_from_slice(&jmpf);
405
406        let code = [initial_bytes, data_section_offset.to_be_bytes().to_vec()].concat();
407
408        Executable::from_bytes(code).convert_to_loader().unwrap();
409    }
410}