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#[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#[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 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 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 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 pub fn code(&self) -> Vec<u8> {
153 self.loader_code().as_bytes().to_vec()
154 }
155
156 pub fn blob(&self) -> Blob {
158 LoaderCode::extract_blob(&self.state.code)
161 .expect("checked before turning into a Executable<Loader>")
162 }
163
164 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 let code = vec![1u8, 2, 3, 4];
218
219 let executable = Executable::<Regular>::from_bytes(code.clone());
221
222 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 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 let executable_result = Executable::<Regular>::load_from(path);
239
240 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 let invalid_path = "/nonexistent/path/to/file";
251
252 let executable_result = Executable::<Regular>::load_from(invalid_path);
254
255 assert!(executable_result.is_err());
257 }
258
259 #[test]
260 fn test_executable_regular_with_configurables() {
261 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 let new_executable = executable.with_configurables(configurables.clone());
268
269 assert_eq!(new_executable.state.configurables, configurables);
271 }
272
273 #[test]
274 fn test_executable_regular_code() {
275 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 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 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 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 let code = vec![1u8, 2]; let executable = Executable::<Regular>::from_bytes(code);
377
378 let result = executable.convert_to_loader();
380
381 assert!(result.is_err());
383 }
384
385 #[test]
386 fn executable_with_no_data_section() {
387 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}