fuels_programs/
executable.rs

1use fuels_core::{
2    types::{
3        errors::Result,
4        transaction_builders::{Blob, BlobTransactionBuilder},
5    },
6    Configurables,
7};
8
9use crate::assembly::script_and_predicate_loader::{
10    extract_data_offset, has_configurables_section_offset,
11};
12use crate::{
13    assembly::script_and_predicate_loader::{extract_configurables_offset, LoaderCode},
14    DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
15};
16
17/// This struct represents a standard executable with its associated bytecode and configurables.
18#[derive(Debug, Clone, PartialEq)]
19pub struct Regular {
20    code: Vec<u8>,
21    configurables: Configurables,
22}
23
24impl Regular {
25    pub fn new(code: Vec<u8>, configurables: Configurables) -> Self {
26        Self {
27            code,
28            configurables,
29        }
30    }
31}
32
33/// Used to transform Script or Predicate code into a loader variant, where the code is uploaded as
34/// a blob and the binary itself is substituted with code that will load the blob code and apply
35/// the given configurables to the Script/Predicate.
36#[derive(Debug, Clone, PartialEq)]
37pub struct Executable<State> {
38    state: State,
39}
40
41impl Executable<Regular> {
42    pub fn from_bytes(code: Vec<u8>) -> Self {
43        Executable {
44            state: Regular::new(code, Default::default()),
45        }
46    }
47
48    /// Loads an `Executable<Regular>` from a file at the given path.
49    ///
50    /// # Parameters
51    ///
52    /// - `path`: The file path to load the executable from.
53    ///
54    /// # Returns
55    ///
56    /// A `Result` containing the `Executable<Regular>` or an error if loading fails.
57    pub fn load_from(path: &str) -> Result<Executable<Regular>> {
58        let code = std::fs::read(path)?;
59
60        Ok(Executable {
61            state: Regular::new(code, Default::default()),
62        })
63    }
64
65    pub fn with_configurables(self, configurables: impl Into<Configurables>) -> Self {
66        Executable {
67            state: Regular {
68                configurables: configurables.into(),
69                ..self.state
70            },
71        }
72    }
73
74    pub fn data_offset_in_code(&self) -> Result<usize> {
75        extract_data_offset(&self.state.code)
76    }
77
78    pub fn configurables_offset_in_code(&self) -> Result<Option<usize>> {
79        if has_configurables_section_offset(&self.state.code)? {
80            Ok(Some(extract_configurables_offset(&self.state.code)?))
81        } else {
82            Ok(None)
83        }
84    }
85
86    /// Returns the code of the executable with configurables applied.
87    ///
88    /// # Returns
89    ///
90    /// The bytecode of the executable with configurables updated.
91    pub fn code(&self) -> Vec<u8> {
92        let mut code = self.state.code.clone();
93        self.state.configurables.update_constants_in(&mut code);
94        code
95    }
96
97    /// Converts the `Executable<Regular>` into an `Executable<Loader>`.
98    ///
99    /// # Returns
100    ///
101    /// A `Result` containing the `Executable<Loader>` or an error if loader code cannot be
102    /// generated for the given binary.
103    pub fn convert_to_loader(self) -> Result<Executable<Loader>> {
104        validate_loader_can_be_made_from_code(
105            self.state.code.clone(),
106            self.state.configurables.clone(),
107        )?;
108
109        Ok(Executable {
110            state: Loader {
111                code: self.state.code,
112                configurables: self.state.configurables,
113            },
114        })
115    }
116}
117
118pub struct Loader {
119    code: Vec<u8>,
120    configurables: Configurables,
121}
122
123impl Executable<Loader> {
124    pub fn with_configurables(self, configurables: impl Into<Configurables>) -> Self {
125        Executable {
126            state: Loader {
127                configurables: configurables.into(),
128                ..self.state
129            },
130        }
131    }
132
133    #[deprecated(note = "Use `configurables_offset_in_code` instead")]
134    pub fn data_offset_in_code(&self) -> usize {
135        self.loader_code().configurables_section_offset()
136    }
137
138    pub fn configurables_offset_in_code(&self) -> usize {
139        self.loader_code().configurables_section_offset()
140    }
141
142    fn loader_code(&self) -> LoaderCode {
143        let mut code = self.state.code.clone();
144
145        self.state.configurables.update_constants_in(&mut code);
146
147        LoaderCode::from_normal_binary(code)
148            .expect("checked before turning into a Executable<Loader>")
149    }
150
151    /// Returns the code of the loader executable with configurables applied.
152    pub fn code(&self) -> Vec<u8> {
153        self.loader_code().as_bytes().to_vec()
154    }
155
156    /// A Blob containing the original executable code minus the data section.
157    pub fn blob(&self) -> Blob {
158        // we don't apply configurables because they touch the data section which isn't part of the
159        // blob
160        LoaderCode::extract_blob(&self.state.code)
161            .expect("checked before turning into a Executable<Loader>")
162    }
163
164    /// Uploads a blob containing the original executable code minus the data section.
165    pub async fn upload_blob(&self, account: impl fuels_accounts::Account) -> Result<()> {
166        let blob = self.blob();
167        let provider = account.try_provider()?;
168
169        if provider.blob_exists(blob.id()).await? {
170            return Ok(());
171        }
172
173        let mut tb = BlobTransactionBuilder::default()
174            .with_blob(self.blob())
175            .with_max_fee_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE);
176
177        account.adjust_for_fee(&mut tb, 0).await?;
178
179        account.add_witnesses(&mut tb)?;
180
181        let tx = tb.build(provider).await?;
182
183        provider
184            .send_transaction_and_await_commit(tx)
185            .await?
186            .check(None)?;
187
188        Ok(())
189    }
190}
191
192fn validate_loader_can_be_made_from_code(
193    mut code: Vec<u8>,
194    configurables: Configurables,
195) -> Result<()> {
196    configurables.update_constants_in(&mut code);
197
198    let _ = LoaderCode::from_normal_binary(code)?;
199
200    Ok(())
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use fuels_core::Configurables;
207    use std::io::Write;
208    use tempfile::NamedTempFile;
209
210    fn legacy_indicating_instruction() -> Vec<u8> {
211        fuel_asm::op::jmpf(0x0, 0x02).to_bytes().to_vec()
212    }
213
214    #[test]
215    fn test_executable_regular_from_bytes() {
216        // Given: Some bytecode
217        let code = vec![1u8, 2, 3, 4];
218
219        // When: Creating an Executable<Regular> from bytes
220        let executable = Executable::<Regular>::from_bytes(code.clone());
221
222        // Then: The executable should have the given code and default configurables
223        assert_eq!(executable.state.code, code);
224        assert_eq!(executable.state.configurables, Default::default());
225    }
226
227    #[test]
228    fn test_executable_regular_load_from() {
229        // Given: A temporary file containing some bytecode
230        let code = vec![5u8, 6, 7, 8];
231        let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
232        temp_file
233            .write_all(&code)
234            .expect("Failed to write to temp file");
235        let path = temp_file.path().to_str().unwrap();
236
237        // When: Loading an Executable<Regular> from the file
238        let executable_result = Executable::<Regular>::load_from(path);
239
240        // Then: The executable should be created successfully with the correct code
241        assert!(executable_result.is_ok());
242        let executable = executable_result.unwrap();
243        assert_eq!(executable.state.code, code);
244        assert_eq!(executable.state.configurables, Default::default());
245    }
246
247    #[test]
248    fn test_executable_regular_load_from_invalid_path() {
249        // Given: An invalid file path
250        let invalid_path = "/nonexistent/path/to/file";
251
252        // When: Attempting to load an Executable<Regular> from the invalid path
253        let executable_result = Executable::<Regular>::load_from(invalid_path);
254
255        // Then: The operation should fail with an error
256        assert!(executable_result.is_err());
257    }
258
259    #[test]
260    fn test_executable_regular_with_configurables() {
261        // Given: An Executable<Regular> and some configurables
262        let code = vec![1u8, 2, 3, 4];
263        let executable = Executable::<Regular>::from_bytes(code);
264        let configurables = Configurables::new(vec![(2, vec![1])]);
265
266        // When: Setting new configurables
267        let new_executable = executable.with_configurables(configurables.clone());
268
269        // Then: The executable should have the new configurables
270        assert_eq!(new_executable.state.configurables, configurables);
271    }
272
273    #[test]
274    fn test_executable_regular_code() {
275        // Given: An Executable<Regular> with some code and configurables
276        let code = vec![1u8, 2, 3, 4];
277        let configurables = Configurables::new(vec![(1, vec![1])]);
278        let executable =
279            Executable::<Regular>::from_bytes(code.clone()).with_configurables(configurables);
280
281        // When: Retrieving the code after applying configurables
282        let modified_code = executable.code();
283
284        assert_eq!(modified_code, vec![1, 1, 3, 4]);
285    }
286
287    #[test]
288    fn test_loader_extracts_code_and_data_section_legacy_format() {
289        let padding = vec![0; 4];
290        let jmpf = legacy_indicating_instruction();
291        let data_offset = 28u64.to_be_bytes().to_vec();
292        let remaining_padding = vec![0; 8];
293        let some_random_instruction = vec![1, 2, 3, 4];
294        let data_section = vec![5, 6, 7, 8];
295
296        let code = [
297            padding.clone(),
298            jmpf.clone(),
299            data_offset.clone(),
300            remaining_padding.clone(),
301            some_random_instruction.clone(),
302            data_section.clone(),
303        ]
304        .concat();
305
306        let executable = Executable::<Regular>::from_bytes(code.clone());
307
308        let loader = executable.convert_to_loader().unwrap();
309
310        let blob = loader.blob();
311        let data_stripped_code = [
312            padding,
313            jmpf.clone(),
314            data_offset,
315            remaining_padding.clone(),
316            some_random_instruction,
317        ]
318        .concat();
319        assert_eq!(blob.as_ref(), data_stripped_code);
320
321        // And: Loader code should match expected binary
322        let loader_code = loader.code();
323
324        assert_eq!(
325            loader_code,
326            LoaderCode::from_normal_binary(code).unwrap().as_bytes()
327        );
328    }
329
330    #[test]
331    fn test_loader_extracts_code_and_configurable_section_new_format() {
332        let padding = vec![0; 4];
333        let jmpf = legacy_indicating_instruction();
334        let data_offset = 28u64.to_be_bytes().to_vec();
335        let configurable_offset = vec![0; 8];
336        let data_section = vec![5, 6, 7, 8];
337        let configurable_section = vec![9, 9, 9, 9];
338
339        let code = [
340            padding.clone(),
341            jmpf.clone(),
342            data_offset.clone(),
343            configurable_offset.clone(),
344            data_section.clone(),
345            configurable_section,
346        ]
347        .concat();
348
349        let executable = Executable::<Regular>::from_bytes(code.clone());
350
351        let loader = executable.convert_to_loader().unwrap();
352
353        let blob = loader.blob();
354        let configurable_stripped_code = [
355            padding,
356            jmpf,
357            data_offset,
358            configurable_offset,
359            data_section,
360        ]
361        .concat();
362        assert_eq!(blob.as_ref(), configurable_stripped_code);
363
364        // And: Loader code should match expected binary
365        let loader_code = loader.code();
366        assert_eq!(
367            loader_code,
368            LoaderCode::from_normal_binary(code).unwrap().as_bytes()
369        );
370    }
371
372    #[test]
373    fn test_executable_regular_convert_to_loader_with_invalid_code() {
374        // Given: An Executable<Regular> with invalid code (too short)
375        let code = vec![1u8, 2]; // Insufficient length for a valid data offset
376        let executable = Executable::<Regular>::from_bytes(code);
377
378        // When: Attempting to convert to a loader
379        let result = executable.convert_to_loader();
380
381        // Then: The conversion should fail with an error
382        assert!(result.is_err());
383    }
384
385    #[test]
386    fn executable_with_no_data_section() {
387        // to skip over the first 2 half words and skip over the offset itself, basically stating
388        // that there is no data section
389        let data_section_offset = 16u64;
390
391        let jmpf = legacy_indicating_instruction();
392        let mut initial_bytes = vec![0; 16];
393        initial_bytes[4..8].copy_from_slice(&jmpf);
394
395        let code = [initial_bytes, data_section_offset.to_be_bytes().to_vec()].concat();
396
397        Executable::from_bytes(code).convert_to_loader().unwrap();
398    }
399}