fuels_programs/
executable.rs

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