Skip to content

Add --generate-hexfile flag to forc build for hex-encoded output #7032

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ pub struct BuildOpts {
pub pkg: PkgOpts,
pub print: PrintOpts,
pub minify: MinifyOpts,
/// If set, generates a JSON file containing the hex-encoded script binary.
pub hex_outfile: Option<String>,
/// If set, outputs a binary file representing the script bytes.
pub binary_outfile: Option<String>,
/// If set, outputs debug info to the provided file.
Expand Down Expand Up @@ -427,6 +429,15 @@ impl BuiltPackage {
Ok(())
}

pub fn write_hexcode(&self, path: &Path) -> Result<()> {
let hex_file = serde_json::json!({
"hex": format!("0x{}", hex::encode(&self.bytecode.bytes)),
});

fs::write(path, hex_file.to_string())?;
Ok(())
}

/// Writes debug_info (source_map) of the BuiltPackage to the given `out_file`.
pub fn write_debug_info(&self, out_file: &Path) -> Result<()> {
if matches!(out_file.extension(), Some(ext) if ext == "json") {
Expand Down Expand Up @@ -505,6 +516,7 @@ impl BuiltPackage {
self.bytecode.bytes.len(),
format_bytecode_size(self.bytecode.bytes.len())
);

// Additional ops required depending on the program type
match self.tree_type {
TreeType::Contract => {
Expand Down Expand Up @@ -2156,6 +2168,7 @@ fn is_contract_dependency(graph: &Graph, node: NodeIx) -> bool {
/// Builds a project with given BuildOptions.
pub fn build_with_options(build_options: &BuildOpts) -> Result<Built> {
let BuildOpts {
hex_outfile,
minify,
binary_outfile,
debug_outfile,
Expand Down Expand Up @@ -2248,6 +2261,16 @@ pub fn build_with_options(build_options: &BuildOpts) -> Result<Built> {
.unwrap_or_else(|| output_dir.join("debug_symbols.obj"));
built_package.write_debug_info(&debug_path)?;
}

if hex_outfile.is_some() {
let hexfile_path = hex_outfile
.as_ref()
.map(|p| output_dir.join(p))
.unwrap_or_else(|| output_dir.join("hex_file.json"));

built_package.write_hexcode(&hexfile_path)?;
}

built_package.write_output(minify, &pkg_manifest.project.name, &output_dir)?;
built_workspace.push(Arc::new(built_package));
}
Expand Down Expand Up @@ -2721,6 +2744,7 @@ pub fn fuel_core_not_running(node_url: &str) -> anyhow::Error {
mod test {
use super::*;
use regex::Regex;
use tempfile::NamedTempFile;

fn setup_build_plan() -> BuildPlan {
let current_dir = env!("CARGO_MANIFEST_DIR");
Expand Down Expand Up @@ -2790,4 +2814,60 @@ mod test {
"#;
assert_eq!(expected, result);
}

#[test]
fn test_write_hexcode() -> Result<()> {
// Create a temporary file for testing
let temp_file = NamedTempFile::new()?;
let path = temp_file.path();

let current_dir = env!("CARGO_MANIFEST_DIR");
let manifest_dir = PathBuf::from(current_dir).parent().unwrap().join(
"test/src/e2e_vm_tests/test_programs/should_pass/forc/workspace_building/test_contract",
);

// Create a test BuiltPackage with some bytecode
let test_bytecode = vec![0x01, 0x02, 0x03, 0x04];
let built_package = BuiltPackage {
descriptor: PackageDescriptor {
name: "test_package".to_string(),
target: BuildTarget::Fuel,
pinned: Pinned {
name: "built_test".to_owned(),
source: source::Pinned::MEMBER,
},
manifest_file: PackageManifestFile::from_dir(manifest_dir)?,
},
program_abi: ProgramABI::Fuel(fuel_abi_types::abi::program::ProgramABI {
program_type: "".to_owned(),
spec_version: "".into(),
encoding_version: "".into(),
concrete_types: vec![],
metadata_types: vec![],
functions: vec![],
configurables: None,
logged_types: None,
messages_types: None,
}),
storage_slots: vec![],
warnings: vec![],
source_map: SourceMap::new(),
tree_type: TreeType::Script,
bytecode: BuiltPackageBytecode {
bytes: test_bytecode,
entries: vec![],
},
bytecode_without_tests: None,
};

// Write the hexcode
built_package.write_hexcode(path)?;

// Read the file and verify its contents
let contents = fs::read_to_string(path)?;
let expected = r#"{"hex":"0x01020304"}"#;
assert_eq!(contents, expected);

Ok(())
}
}
1 change: 1 addition & 0 deletions forc-plugins/forc-client/src/op/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,7 @@ fn build_opts_from_cmd(cmd: &cmd::Deploy, member_filter: pkg::MemberFilter) -> p
error_on_warnings: false,
binary_outfile: cmd.build_output.bin_file.clone(),
debug_outfile: cmd.build_output.debug_file.clone(),
hex_outfile: cmd.build_output.hex_file.clone(),
build_target: BuildTarget::default(),
tests: false,
member_filter,
Expand Down
1 change: 1 addition & 0 deletions forc-plugins/forc-client/src/op/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ fn build_opts_from_cmd(cmd: &cmd::Run) -> pkg::BuildOpts {
metrics_outfile: cmd.print.metrics_outfile.clone(),
binary_outfile: cmd.build_output.bin_file.clone(),
debug_outfile: cmd.build_output.debug_file.clone(),
hex_outfile: cmd.build_output.hex_file.clone(),
tests: false,
member_filter: pkg::MemberFilter::only_scripts(),
experimental: cmd.experimental.experimental.clone(),
Expand Down
4 changes: 4 additions & 0 deletions forc-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ pub struct TestOpts {
/// If the argument provided ends with .json, a JSON is emitted,
/// otherwise, an ELF file containing DWARF is emitted.
pub debug_outfile: Option<String>,
/// If set, generates a JSON file containing the hex-encoded script binary.
pub hex_outfile: Option<String>,
/// Build target to use.
pub build_target: BuildTarget,
/// Name of the build profile to use.
Expand Down Expand Up @@ -447,6 +449,7 @@ impl From<TestOpts> for pkg::BuildOpts {
minify: val.minify,
binary_outfile: val.binary_outfile,
debug_outfile: val.debug_outfile,
hex_outfile: val.hex_outfile,
build_target: val.build_target,
build_profile: val.build_profile,
release: val.release,
Expand All @@ -471,6 +474,7 @@ impl TestOpts {
minify: self.minify,
binary_outfile: self.binary_outfile,
debug_outfile: self.debug_outfile,
hex_outfile: self.hex_outfile,
build_target: self.build_target,
build_profile: self.build_profile,
release: self.release,
Expand Down
1 change: 1 addition & 0 deletions forc/src/cli/commands/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ fn opts_from_cmd(cmd: Command) -> forc_test::TestOpts {
error_on_warnings: cmd.build.profile.error_on_warnings,
binary_outfile: cmd.build.output.bin_file,
debug_outfile: cmd.build.output.debug_file,
hex_outfile: cmd.build.output.hex_file,
build_target: cmd.build.build_target,
experimental: cmd.experimental.experimental,
no_experimental: cmd.experimental.no_experimental,
Expand Down
4 changes: 4 additions & 0 deletions forc/src/cli/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub struct BuildOutput {
/// If the file extension is .json, JSON format is used. Otherwise, an .elf file containing DWARF format is emitted.
#[clap(long = "output-debug", short = 'g')]
pub debug_file: Option<String>,

/// Generates a JSON file containing the hex-encoded script binary.
#[clap(long = "output-hexfile")]
pub hex_file: Option<String>,
}

/// Build profile options.
Expand Down
1 change: 1 addition & 0 deletions forc/src/ops/forc_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fn opts_from_cmd(cmd: BuildCommand) -> pkg::BuildOpts {
error_on_warnings: cmd.build.profile.error_on_warnings,
binary_outfile: cmd.build.output.bin_file,
debug_outfile: cmd.build.output.debug_file,
hex_outfile: cmd.build.output.hex_file,
build_target: cmd.build.build_target,
tests: cmd.tests,
member_filter: MemberFilter::default(),
Expand Down
1 change: 1 addition & 0 deletions forc/src/ops/forc_contract_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ fn build_opts_from_cmd(cmd: &ContractIdCommand) -> pkg::BuildOpts {
error_on_warnings: cmd.build_profile.error_on_warnings,
binary_outfile: cmd.build_output.bin_file.clone(),
debug_outfile: cmd.build_output.debug_file.clone(),
hex_outfile: cmd.build_output.hex_file.clone(),
build_target: BuildTarget::default(),
tests: false,
member_filter: pkg::MemberFilter::only_contracts(),
Expand Down
1 change: 1 addition & 0 deletions forc/src/ops/forc_predicate_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn build_opts_from_cmd(cmd: PredicateRootCommand) -> pkg::BuildOpts {
error_on_warnings: cmd.build_profile.error_on_warnings,
binary_outfile: cmd.build_output.bin_file.clone(),
debug_outfile: cmd.build_output.debug_file,
hex_outfile: cmd.build_output.hex_file.clone(),
build_target: BuildTarget::default(),
tests: false,
member_filter: pkg::MemberFilter::only_predicates(),
Expand Down