diff --git a/e2e/assets/precompiled_sway/legacy_format_simple_contract.bin b/e2e/assets/precompiled_sway/legacy_format_simple_contract.bin new file mode 100644 index 000000000..d4113f0b3 Binary files /dev/null and b/e2e/assets/precompiled_sway/legacy_format_simple_contract.bin differ diff --git a/e2e/tests/binary_format.rs b/e2e/tests/binary_format.rs new file mode 100644 index 000000000..79fd59ce8 --- /dev/null +++ b/e2e/tests/binary_format.rs @@ -0,0 +1,80 @@ +#[cfg(test)] +mod tests { + use fuels::programs::executable::{Executable, Regular}; + use std::convert::TryInto; + use std::ops::Range; + + const DATA_OFFSET_LOCATION: Range = 8..16; + const CONFIGURABLES_OFFSET_LOCATION: Range = 16..24; + + const LEGACY_BINARY_PATH: &str = + "../e2e/assets/precompiled_sway/legacy_format_simple_contract.bin"; + const NEW_BINARY_PATH: &str = + "../e2e/sway/bindings/simple_contract/out/release/simple_contract.bin"; + + #[test] + fn no_configurables_offset_for_old_sway_binaries() { + // given + let (_, executable) = load(LEGACY_BINARY_PATH); + + // when + let configurables_offset = executable.configurables_offset_in_code().unwrap(); + + // then + assert_eq!(configurables_offset, None); + } + + #[test] + fn correct_data_offset_for_old_sway_binaries() { + // given + let (binary, executable) = load(LEGACY_BINARY_PATH); + let expected_data_offset = read_offset(&binary, DATA_OFFSET_LOCATION); + + // when + let data_offset = executable.data_offset_in_code().unwrap(); + + // then + assert_eq!(data_offset, expected_data_offset); + } + + #[test] + fn correct_data_offset_for_new_sway_binaries() { + // given + let (binary, executable) = load(NEW_BINARY_PATH); + let expected_data_offset = read_offset(&binary, DATA_OFFSET_LOCATION); + + // when + let data_offset = executable.data_offset_in_code().unwrap(); + + // then + assert_eq!(data_offset, expected_data_offset); + } + + #[test] + fn correct_configurables_offset_for_new_sway_binaries() { + // given + let (binary, executable) = load(NEW_BINARY_PATH); + let expected_configurables_offset = read_offset(&binary, CONFIGURABLES_OFFSET_LOCATION); + + // when + let configurables_offset = executable.configurables_offset_in_code(); + + // then + let configurables_offset = configurables_offset + .expect("to successfully detect a modern binary is used") + .expect("to extract the configurables_offset"); + assert_eq!(configurables_offset, expected_configurables_offset); + } + + pub fn read_offset(binary: &[u8], range: Range) -> usize { + assert_eq!(range.clone().count(), 8, "must be a range of 8 B"); + let data: [u8; 8] = binary[range].try_into().unwrap(); + u64::from_be_bytes(data) as usize + } + + fn load(path: &str) -> (Vec, Executable) { + let binary = std::fs::read(path).unwrap(); + let executable = Executable::from_bytes(binary.clone()); + (binary, executable) + } +} diff --git a/e2e/tests/debug_utils.rs b/e2e/tests/debug_utils.rs index 07ede3feb..c1faa8049 100644 --- a/e2e/tests/debug_utils.rs +++ b/e2e/tests/debug_utils.rs @@ -386,29 +386,31 @@ async fn debugs_sway_script_with_no_configurables() -> Result<()> { .await .unwrap(); - let abi = - std::fs::read_to_string("./sway/scripts/basic_script/out/release/basic_script-abi.json")?; - - let decoder = ABIFormatter::from_json_abi(abi)?; - let ScriptType::Other(desc) = ScriptType::detect(&tb.script, &tb.script_data).unwrap() else { panic!("expected a script") }; - assert_eq!( - decoder - .decode_configurables(desc.data_section().unwrap()) - .unwrap(), - vec![] - ); + assert!(desc.data_section().is_none()); Ok(()) } +fn generate_modern_sway_binary(len: usize) -> Vec { + assert!( + len > 24, + "needs at least 24B to fit in the indicator_of_modern_binary, data & configurables offsets" + ); + + let mut custom_script = vec![0; len]; + let indicator_of_modern_binary = fuel_asm::op::jmpf(0x00, 0x04); + + custom_script[4..8].copy_from_slice(&indicator_of_modern_binary.to_bytes()); + custom_script +} #[tokio::test] async fn data_section_offset_not_set_if_out_of_bounds() -> Result<()> { - let mut custom_script = vec![0; 1000]; - custom_script[8..16].copy_from_slice(&u64::MAX.to_be_bytes()); + let mut custom_script = generate_modern_sway_binary(100); + custom_script[16..24].copy_from_slice(&u64::MAX.to_be_bytes()); let ScriptType::Other(desc) = ScriptType::detect(&custom_script, &[]).unwrap() else { panic!("expected a script") diff --git a/e2e/tests/scripts.rs b/e2e/tests/scripts.rs index ef1b865de..3a7d2fbce 100644 --- a/e2e/tests/scripts.rs +++ b/e2e/tests/scripts.rs @@ -657,10 +657,14 @@ async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() let configurables: Configurables = configurables.into(); + let offset = regular + .configurables_offset_in_code()? + .unwrap_or_else(|| regular.data_offset_in_code().unwrap()); + let shifted_configurables = configurables - .with_shifted_offsets(-(regular.data_offset_in_code().unwrap() as i64)) + .with_shifted_offsets(-(offset as i64)) .unwrap() - .with_shifted_offsets(loader.data_offset_in_code() as i64) + .with_shifted_offsets(loader.configurables_offset_in_code() as i64) .unwrap(); let loader_posing_as_normal_script = diff --git a/packages/fuels-programs/src/assembly/contract_call.rs b/packages/fuels-programs/src/assembly/contract_call.rs index 9611907f9..d938d5e7d 100644 --- a/packages/fuels-programs/src/assembly/contract_call.rs +++ b/packages/fuels-programs/src/assembly/contract_call.rs @@ -3,6 +3,8 @@ use fuel_tx::{AssetId, ContractId}; use fuels_core::{constants::WORD_SIZE, error, types::errors::Result}; use super::cursor::WasmFriendlyCursor; +#[derive(Debug)] + pub struct ContractCallInstructions { instructions: Vec, gas_fwd: bool, diff --git a/packages/fuels-programs/src/assembly/script_and_predicate_loader.rs b/packages/fuels-programs/src/assembly/script_and_predicate_loader.rs index 7909c3c4a..90beab97a 100644 --- a/packages/fuels-programs/src/assembly/script_and_predicate_loader.rs +++ b/packages/fuels-programs/src/assembly/script_and_predicate_loader.rs @@ -1,3 +1,12 @@ +//! # Loader Module +//! +//! This module provides functionality for loading and processing binaries generated by Sway. +//! **Important:** All functions within this module assume the binary adheres to the structure +//! produced by Sway. Using binaries generated by other means (e.g., manually constructed binaries) +//! may lead to unexpected or incorrect results. +//! +//! For more information on Sway, please visit the [Sway GitHub repository](https://github.com/FuelLabs/sway). + use fuel_asm::{op, Instruction, RegId}; use fuels_core::{constants::WORD_SIZE, types::errors::Result}; use itertools::Itertools; @@ -7,7 +16,7 @@ use crate::assembly::cursor::WasmFriendlyCursor; pub struct LoaderCode { blob_id: [u8; 32], code: Vec, - data_offset: usize, + section_offset: usize, } impl LoaderCode { @@ -15,23 +24,23 @@ impl LoaderCode { // nostd friendly #[cfg(feature = "std")] pub fn from_normal_binary(binary: Vec) -> Result { - let (original_code, data_section) = split_at_data_offset(&binary)?; + let (original_code, split_section) = split_for_loader(&binary)?; let blob_id = fuels_core::types::transaction_builders::Blob::from(original_code.to_vec()).id(); - let (loader_code, data_offset) = Self::generate_loader_code(blob_id, data_section); + let (loader_code, section_offset) = Self::generate_loader_code(blob_id, split_section); Ok(Self { blob_id, code: loader_code, - data_offset, + section_offset, }) } pub fn from_loader_binary(binary: &[u8]) -> Result> { - if let Some((blob_id, data_section_offset)) = extract_blob_id_and_data_offset(binary)? { + if let Some((blob_id, section_offset)) = extract_blob_id_and_section_offset(binary)? { Ok(Some(Self { - data_offset: data_section_offset, + section_offset, code: binary.to_vec(), blob_id, })) @@ -42,7 +51,7 @@ impl LoaderCode { #[cfg(feature = "std")] pub fn extract_blob(binary: &[u8]) -> Result { - let (code, _) = split_at_data_offset(binary)?; + let (code, _) = split_for_loader(binary)?; Ok(code.to_vec().into()) } @@ -50,15 +59,15 @@ impl LoaderCode { &self.code } - pub fn data_section_offset(&self) -> usize { - self.data_offset + pub fn configurables_section_offset(&self) -> usize { + self.section_offset } - fn generate_loader_code(blob_id: [u8; 32], data_section: &[u8]) -> (Vec, usize) { - if !data_section.is_empty() { - generate_loader_w_data_section(blob_id, data_section) + fn generate_loader_code(blob_id: [u8; 32], split_section: &[u8]) -> (Vec, usize) { + if !split_section.is_empty() { + generate_loader_w_configurables(blob_id, split_section) } else { - generate_loader_wo_data_section(blob_id) + generate_loader_wo_configurables(blob_id) } } @@ -67,29 +76,30 @@ impl LoaderCode { } } -fn extract_blob_id_and_data_offset(binary: &[u8]) -> Result> { - let (has_data_section, mut cursor) = - if let Some(cursor) = consume_instructions(binary, &loader_instructions_w_data_section()) { - (true, cursor) - } else if let Some(cursor) = - consume_instructions(binary, &loader_instructions_no_data_section()) - { - (false, cursor) - } else { - return Ok(None); - }; +fn extract_blob_id_and_section_offset(binary: &[u8]) -> Result> { + let (has_configurables, mut cursor) = if let Some(cursor) = + consume_instructions(binary, &loader_instructions_w_configurables()) + { + (true, cursor) + } else if let Some(cursor) = + consume_instructions(binary, &loader_instructions_no_configurables()) + { + (false, cursor) + } else { + return Ok(None); + }; let blob_id = cursor.consume_fixed("blob id")?; - if has_data_section { - let _data_section_len = cursor.consume(WORD_SIZE, "data section len")?; + if has_configurables { + let _section_len = cursor.consume(WORD_SIZE, "section with configurables len")?; } - let data_section_offset = binary + let section_offset = binary .len() .checked_sub(cursor.unconsumed()) .expect("must be less or eq"); - Ok(Some((blob_id, data_section_offset))) + Ok(Some((blob_id, section_offset))) } fn consume_instructions<'a>( @@ -114,8 +124,8 @@ fn consume_instructions<'a>( .then_some(script_cursor) } -fn generate_loader_wo_data_section(blob_id: [u8; 32]) -> (Vec, usize) { - let instruction_bytes = loader_instructions_no_data_section() +fn generate_loader_wo_configurables(blob_id: [u8; 32]) -> (Vec, usize) { + let instruction_bytes = loader_instructions_no_configurables() .into_iter() .flat_map(|instruction| instruction.to_bytes()); @@ -123,45 +133,48 @@ fn generate_loader_wo_data_section(blob_id: [u8; 32]) -> (Vec, usize) { .chain(blob_id.iter().copied()) .collect_vec(); // there is no data section, so we point the offset to the end of the file - let new_data_section_offset = code.len(); + let new_section_offset = code.len(); - (code, new_data_section_offset) + (code, new_section_offset) } -fn generate_loader_w_data_section(blob_id: [u8; 32], data_section: &[u8]) -> (Vec, usize) { +fn generate_loader_w_configurables( + blob_id: [u8; 32], + section_w_configurables: &[u8], +) -> (Vec, usize) { // The final code is going to have this structure: // 1. loader instructions // 2. blob id - // 3. length_of_data_section - // 4. the data_section (updated with configurables as needed) + // 3. length_of_section_containing_configurables + // 4. the section with configurables (updated with configurables as needed) - let instruction_bytes = loader_instructions_w_data_section() + let instruction_bytes = loader_instructions_w_configurables() .into_iter() .flat_map(|instruction| instruction.to_bytes()) .collect_vec(); let blob_bytes = blob_id.iter().copied().collect_vec(); - let original_data_section_len_encoded = u64::try_from(data_section.len()) + let original_section_len_encoded = u64::try_from(section_w_configurables.len()) .expect("data section to be less than u64::MAX") .to_be_bytes(); - // The data section is placed after all of the instructions, the BlobId, and the number representing + // The section with configurables is placed after all of the instructions, the BlobId, and the number representing // how big the data section is. - let new_data_section_offset = - instruction_bytes.len() + blob_bytes.len() + original_data_section_len_encoded.len(); + let new_section_offset = + instruction_bytes.len() + blob_bytes.len() + original_section_len_encoded.len(); let code = instruction_bytes .into_iter() .chain(blob_bytes) - .chain(original_data_section_len_encoded) - .chain(data_section.to_vec()) + .chain(original_section_len_encoded) + .chain(section_w_configurables.to_vec()) .collect(); - (code, new_data_section_offset) + (code, new_section_offset) } -fn loader_instructions_no_data_section() -> [Instruction; 8] { +fn loader_instructions_no_configurables() -> [Instruction; 8] { const REG_ADDRESS_OF_DATA_AFTER_CODE: u8 = 0x10; const REG_START_OF_LOADED_CODE: u8 = 0x11; const REG_GENERAL_USE: u8 = 0x12; @@ -207,7 +220,7 @@ fn loader_instructions_no_data_section() -> [Instruction; 8] { instructions } -pub fn loader_instructions_w_data_section() -> [Instruction; 12] { +pub fn loader_instructions_w_configurables() -> [Instruction; 12] { const BLOB_ID_SIZE: u16 = 32; const REG_ADDRESS_OF_DATA_AFTER_CODE: u8 = 0x10; const REG_START_OF_LOADED_CODE: u8 = 0x11; @@ -273,6 +286,32 @@ pub fn loader_instructions_w_data_section() -> [Instruction; 12] { instructions } +pub fn extract_configurables_offset(binary: &[u8]) -> Result { + if binary.len() < 24 { + return Err(fuels_core::error!( + Other, + "given binary is too short to contain a configurable offset, len: {}", + binary.len() + )); + } + + let configurable_offset: [u8; 8] = binary[16..24].try_into().expect("checked above"); + Ok(u64::from_be_bytes(configurable_offset) as usize) +} + +pub fn split_at_configurables_offset(binary: &[u8]) -> Result<(&[u8], &[u8])> { + let offset = extract_configurables_offset(binary)?; + if binary.len() < offset { + return Err(fuels_core::error!( + Other, + "configurables section offset is out of bounds, offset: {offset}, binary len: {}", + binary.len() + )); + } + + Ok(binary.split_at(offset)) +} + pub fn extract_data_offset(binary: &[u8]) -> Result { if binary.len() < 16 { return Err(fuels_core::error!( @@ -283,7 +322,6 @@ pub fn extract_data_offset(binary: &[u8]) -> Result { } let data_offset: [u8; 8] = binary[8..16].try_into().expect("checked above"); - Ok(u64::from_be_bytes(data_offset) as usize) } @@ -296,6 +334,55 @@ pub fn split_at_data_offset(binary: &[u8]) -> Result<(&[u8], &[u8])> { binary.len() )); } - Ok(binary.split_at(offset)) } + +pub fn split_for_loader(binary: &[u8]) -> Result<(&[u8], &[u8])> { + // First determine if it's a legacy binary + if has_configurables_section_offset(binary)? { + split_at_configurables_offset(binary) + } else { + split_at_data_offset(binary) + } +} + +pub fn get_offset_for_section_containing_configurables(binary: &[u8]) -> Result { + if has_configurables_section_offset(binary).unwrap_or(true) { + extract_configurables_offset(binary) + } else { + extract_data_offset(binary) + } +} + +pub fn has_configurables_section_offset(binary: &[u8]) -> Result { + let slice = binary.get(4..8).ok_or_else(|| { + fuels_core::error!( + Other, + "binary too short to check JMPF instruction, need at least 8 bytes but got: {}", + binary.len() + ) + })?; + + let instruction_bytes: [u8; 4] = slice + .try_into() + .map_err(|_| fuels_core::error!(Other, "Failed to convert slice to [u8; 4]"))?; + + match Instruction::try_from(instruction_bytes) + .map_err(|e| fuels_core::error!(Other, "Invalid instruction at byte 4: {:?}", e))? + { + Instruction::JMPF(offset) => match offset.imm18().to_u32() { + 0x04 => Ok(true), + 0x02 => Ok(false), + other => Err(fuels_core::error!( + Other, + "invalid JMPF offset, expected 0x02 or 0x04, got: {:#04x}", + other + )), + }, + inst => Err(fuels_core::error!( + Other, + "expected JMPF instruction, got: {:?}", + inst + )), + } +} diff --git a/packages/fuels-programs/src/debug.rs b/packages/fuels-programs/src/debug.rs index 105f7335d..0f76af25b 100644 --- a/packages/fuels-programs/src/debug.rs +++ b/packages/fuels-programs/src/debug.rs @@ -2,6 +2,7 @@ use fuel_asm::{Instruction, Opcode}; use fuels_core::{error, types::errors::Result}; use itertools::Itertools; +use crate::assembly::script_and_predicate_loader::get_offset_for_section_containing_configurables; use crate::{ assembly::{ contract_call::{ContractCallData, ContractCallInstructions}, @@ -13,6 +14,8 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct ScriptCallData { pub code: Vec, + /// This will be renamed in next breaking release. For binary generated with sway 0.66.5 this will be data_offset + /// and for binary generated with sway 0.66.6 and above this will probably be data_section_offset and configurable_section_offset. pub data_section_offset: Option, pub data: Vec, } @@ -36,23 +39,24 @@ pub enum ScriptType { Other(ScriptCallData), } -fn parse_script_call(script: &[u8], script_data: &[u8]) -> ScriptCallData { +fn parse_script_call(script: &[u8], script_data: &[u8]) -> Result { let data_section_offset = if script.len() >= 16 { - let data_offset = u64::from_be_bytes(script[8..16].try_into().expect("will have 8 bytes")); - if data_offset as usize >= script.len() { + let offset = get_offset_for_section_containing_configurables(script)?; + + if offset >= script.len() { None } else { - Some(data_offset) + Some(offset as u64) } } else { None }; - ScriptCallData { + Ok(ScriptCallData { data: script_data.to_vec(), data_section_offset, code: script.to_vec(), - } + }) } fn parse_contract_calls( @@ -141,7 +145,7 @@ impl ScriptType { return Ok(Self::Loader { script, blob_id }); } - Ok(Self::Other(parse_script_call(script, data))) + Ok(Self::Other(parse_script_call(script, data)?)) } } @@ -156,7 +160,7 @@ fn parse_loader_script(script: &[u8], data: &[u8]) -> Result>(); - - // when - let script_type = ScriptType::detect(&handwritten_script, &[]).unwrap(); - - // then - assert_eq!( - script_type, - ScriptType::Other(ScriptCallData { - code: handwritten_script.to_vec(), - data_section_offset: None, - data: vec![] - }) - ); - } - fn example_contract_call_data(has_args: bool, gas_fwd: bool) -> Vec { let mut data = vec![]; data.extend_from_slice(&100u64.to_be_bytes()); @@ -342,7 +317,7 @@ mod tests { #[test] fn loader_script_without_a_blob() { // given - let script = loader_instructions_w_data_section() + let script = loader_instructions_w_configurables() .iter() .flat_map(|i| i.to_bytes()) .collect::>(); @@ -363,7 +338,7 @@ mod tests { #[test] fn loader_script_with_almost_matching_instructions() { // given - let mut loader_instructions = loader_instructions_w_data_section().to_vec(); + let mut loader_instructions = loader_instructions_w_configurables().to_vec(); loader_instructions.insert( loader_instructions.len() - 2, diff --git a/packages/fuels-programs/src/executable.rs b/packages/fuels-programs/src/executable.rs index 28d6d5a77..06be05c77 100644 --- a/packages/fuels-programs/src/executable.rs +++ b/packages/fuels-programs/src/executable.rs @@ -6,8 +6,11 @@ use fuels_core::{ Configurables, }; +use crate::assembly::script_and_predicate_loader::{ + extract_data_offset, has_configurables_section_offset, +}; use crate::{ - assembly::script_and_predicate_loader::{extract_data_offset, LoaderCode}, + assembly::script_and_predicate_loader::{extract_configurables_offset, LoaderCode}, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE, }; @@ -72,6 +75,14 @@ impl Executable { extract_data_offset(&self.state.code) } + pub fn configurables_offset_in_code(&self) -> Result> { + if has_configurables_section_offset(&self.state.code)? { + Ok(Some(extract_configurables_offset(&self.state.code)?)) + } else { + Ok(None) + } + } + /// Returns the code of the executable with configurables applied. /// /// # Returns @@ -119,8 +130,13 @@ impl Executable { } } + #[deprecated(note = "Use `configurables_offset_in_code` instead")] pub fn data_offset_in_code(&self) -> usize { - self.loader_code().data_section_offset() + self.loader_code().configurables_section_offset() + } + + pub fn configurables_offset_in_code(&self) -> usize { + self.loader_code().configurables_section_offset() } fn loader_code(&self) -> LoaderCode { @@ -191,6 +207,10 @@ mod tests { use std::io::Write; use tempfile::NamedTempFile; + fn legacy_indicating_instruction() -> Vec { + fuel_asm::op::jmpf(0x0, 0x02).to_bytes().to_vec() + } + #[test] fn test_executable_regular_from_bytes() { // Given: Some bytecode @@ -265,33 +285,88 @@ mod tests { } #[test] - fn test_loader_extracts_code_and_data_section_correctly() { - // Given: An Executable with valid code - let padding = vec![0; 8]; - let offset = 20u64.to_be_bytes().to_vec(); + fn test_loader_extracts_code_and_data_section_legacy_format() { + let padding = vec![0; 4]; + let jmpf = legacy_indicating_instruction(); + let data_offset = 28u64.to_be_bytes().to_vec(); + let remaining_padding = vec![0; 8]; let some_random_instruction = vec![1, 2, 3, 4]; let data_section = vec![5, 6, 7, 8]; + let code = [ padding.clone(), - offset.clone(), + jmpf.clone(), + data_offset.clone(), + remaining_padding.clone(), some_random_instruction.clone(), - data_section, + data_section.clone(), ] .concat(); + let executable = Executable::::from_bytes(code.clone()); - // When: Converting to a loader let loader = executable.convert_to_loader().unwrap(); let blob = loader.blob(); - let data_stripped_code = [padding, offset, some_random_instruction].concat(); + let data_stripped_code = [ + padding, + jmpf.clone(), + data_offset, + remaining_padding.clone(), + some_random_instruction, + ] + .concat(); assert_eq!(blob.as_ref(), data_stripped_code); + // And: Loader code should match expected binary + let loader_code = loader.code(); + + assert_eq!( + loader_code, + LoaderCode::from_normal_binary(code).unwrap().as_bytes() + ); + } + + #[test] + fn test_loader_extracts_code_and_configurable_section_new_format() { + let padding = vec![0; 4]; + let jmpf = legacy_indicating_instruction(); + let data_offset = 28u64.to_be_bytes().to_vec(); + let configurable_offset = vec![0; 8]; + let data_section = vec![5, 6, 7, 8]; + let configurable_section = vec![9, 9, 9, 9]; + + let code = [ + padding.clone(), + jmpf.clone(), + data_offset.clone(), + configurable_offset.clone(), + data_section.clone(), + configurable_section, + ] + .concat(); + + let executable = Executable::::from_bytes(code.clone()); + + let loader = executable.convert_to_loader().unwrap(); + + let blob = loader.blob(); + let configurable_stripped_code = [ + padding, + jmpf, + data_offset, + configurable_offset, + data_section, + ] + .concat(); + assert_eq!(blob.as_ref(), configurable_stripped_code); + + // And: Loader code should match expected binary let loader_code = loader.code(); assert_eq!( loader_code, LoaderCode::from_normal_binary(code).unwrap().as_bytes() - ) + ); } #[test] @@ -313,7 +388,11 @@ mod tests { // that there is no data section let data_section_offset = 16u64; - let code = [vec![0; 8], data_section_offset.to_be_bytes().to_vec()].concat(); + let jmpf = legacy_indicating_instruction(); + let mut initial_bytes = vec![0; 16]; + initial_bytes[4..8].copy_from_slice(&jmpf); + + let code = [initial_bytes, data_section_offset.to_be_bytes().to_vec()].concat(); Executable::from_bytes(code).convert_to_loader().unwrap(); } diff --git a/scripts/change-log/src/get_full_changelog.rs b/scripts/change-log/src/get_full_changelog.rs index a7c96a66a..bcc4fa117 100644 --- a/scripts/change-log/src/get_full_changelog.rs +++ b/scripts/change-log/src/get_full_changelog.rs @@ -53,8 +53,7 @@ pub async fn get_changelog_info( .as_ref() .map_or("misc", |title| title.split(':').next().unwrap_or("misc")) .to_string(); - let is_breaking = pr.title.as_ref().map_or(false, |title| title.contains('!')); - + let is_breaking = pr.title.as_ref().is_some_and(|title| title.contains('!')); let title_description = pr .title .as_ref()