From e745618221c4114c15ab46ce7e7a7f3a5885f7ef Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Tue, 31 Oct 2023 20:52:28 +0100 Subject: [PATCH] Include runtime_bytecode in artifacts --- crates/driver/src/lib.rs | 42 +++++++++++++++++++++++------- crates/fe/src/task/build.rs | 40 +++++++++++++++++++++------- crates/test-utils/src/lib.rs | 25 +++++++++++------- crates/tests-legacy/src/crashes.rs | 2 +- crates/yulc/src/lib.rs | 28 ++++++++++++++++---- newsfragments/947.feature.md | 16 ++++++++++++ 6 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 newsfragments/947.feature.md diff --git a/crates/driver/src/lib.rs b/crates/driver/src/lib.rs index a31b1f3307..ac381d5a8c 100644 --- a/crates/driver/src/lib.rs +++ b/crates/driver/src/lib.rs @@ -28,6 +28,8 @@ pub struct CompiledContract { pub yul: String, #[cfg(feature = "solc-backend")] pub bytecode: String, + #[cfg(feature = "solc-backend")] + pub runtime_bytecode: String, } #[cfg(feature = "solc-backend")] @@ -111,13 +113,14 @@ pub fn compile_single_file( path: &str, src: &str, with_bytecode: bool, + with_runtime_bytecode: bool, optimize: bool, ) -> Result { let module = ModuleId::new_standalone(db, path, src); let diags = module.diagnostics(db); if diags.is_empty() { - compile_module(db, module, with_bytecode, optimize) + compile_module(db, module, with_bytecode, with_runtime_bytecode, optimize) } else { Err(CompileError(diags)) } @@ -158,6 +161,7 @@ pub fn compile_ingot( db: &mut Db, build_files: &BuildFiles, with_bytecode: bool, + with_runtime_bytecode: bool, optimize: bool, ) -> Result { let ingot = IngotId::from_build_files(db, build_files); @@ -170,7 +174,13 @@ pub fn compile_ingot( let main_module = ingot .root_module(db) .expect("missing root module, with no diagnostic"); - compile_module(db, main_module, with_bytecode, optimize) + compile_module( + db, + main_module, + with_bytecode, + with_runtime_bytecode, + optimize, + ) } #[cfg(feature = "solc-backend")] @@ -220,7 +230,7 @@ fn compile_test(db: &mut Db, test: FunctionId, optimize: bool) -> CompiledTest { let yul_test = fe_codegen::yul::isel::lower_test(db, test) .to_string() .replace('"', "\\\""); - let bytecode = compile_to_evm("test", &yul_test, optimize); + let bytecode = compile_to_evm("test", &yul_test, optimize, false).bytecode; let events = db.codegen_abi_module_events(test.module(db)); CompiledTest::new(test.name(db), events, bytecode) } @@ -239,6 +249,7 @@ fn compile_module( db: &mut Db, module_id: ModuleId, with_bytecode: bool, + with_runtime_bytecode: bool, optimize: bool, ) -> Result { let mut contracts = IndexMap::default(); @@ -248,11 +259,17 @@ fn compile_module( let abi = db.codegen_abi_contract(contract); let yul_contract = compile_to_yul(db, contract); - let bytecode = if with_bytecode { + let (bytecode, runtime_bytecode) = if with_bytecode || with_runtime_bytecode { let deployable_name = db.codegen_contract_deployer_symbol_name(contract); - compile_to_evm(deployable_name.as_str(), &yul_contract, optimize) + let bytecode = compile_to_evm( + deployable_name.as_str(), + &yul_contract, + optimize, + with_runtime_bytecode, + ); + (bytecode.bytecode, bytecode.runtime_bytecode) } else { - "".to_string() + ("".to_string(), "".to_string()) }; contracts.insert( @@ -261,6 +278,7 @@ fn compile_module( json_abi: serde_json::to_string_pretty(&abi).unwrap(), yul: yul_contract, bytecode, + runtime_bytecode, }, ); } @@ -277,6 +295,7 @@ fn compile_module( db: &mut Db, module_id: ModuleId, _with_bytecode: bool, + _with_runtime_bytecode: bool, _optimize: bool, ) -> Result { let mut contracts = IndexMap::default(); @@ -307,9 +326,14 @@ fn compile_to_yul(db: &mut Db, contract: ContractId) -> String { } #[cfg(feature = "solc-backend")] -fn compile_to_evm(name: &str, yul_object: &str, optimize: bool) -> String { - match fe_yulc::compile_single_contract(name, yul_object, optimize) { - Ok(contracts) => contracts, +fn compile_to_evm( + name: &str, + yul_object: &str, + optimize: bool, + verify_runtime_bytecode: bool, +) -> fe_yulc::ContractBytecode { + match fe_yulc::compile_single_contract(name, yul_object, optimize, verify_runtime_bytecode) { + Ok(bytecode) => bytecode, Err(error) => { for error in serde_json::from_str::(&error.0) diff --git a/crates/fe/src/task/build.rs b/crates/fe/src/task/build.rs index 16ed066efd..07343c6c2e 100644 --- a/crates/fe/src/task/build.rs +++ b/crates/fe/src/task/build.rs @@ -16,6 +16,7 @@ enum Emit { Ast, LoweredAst, Bytecode, + RuntimeBytecode, Tokens, Yul, } @@ -45,6 +46,7 @@ pub struct BuildArgs { fn build_single_file(compile_arg: &BuildArgs) -> (String, CompiledModule) { let emit = &compile_arg.emit; let with_bytecode = emit.contains(&Emit::Bytecode); + let with_runtime_bytecode = emit.contains(&Emit::RuntimeBytecode); let input_path = &compile_arg.input_path; let optimize = compile_arg.optimize.unwrap_or(true); @@ -62,6 +64,7 @@ fn build_single_file(compile_arg: &BuildArgs) -> (String, CompiledModule) { input_path, &content, with_bytecode, + with_runtime_bytecode, optimize, ) { Ok(module) => module, @@ -77,6 +80,7 @@ fn build_single_file(compile_arg: &BuildArgs) -> (String, CompiledModule) { fn build_ingot(compile_arg: &BuildArgs) -> (String, CompiledModule) { let emit = &compile_arg.emit; let with_bytecode = emit.contains(&Emit::Bytecode); + let with_runtime_bytecode = emit.contains(&Emit::RuntimeBytecode); let input_path = &compile_arg.input_path; let optimize = compile_arg.optimize.unwrap_or(true); @@ -100,15 +104,20 @@ fn build_ingot(compile_arg: &BuildArgs) -> (String, CompiledModule) { } let mut db = fe_driver::Db::default(); - let compiled_module = - match fe_driver::compile_ingot(&mut db, &build_files, with_bytecode, optimize) { - Ok(module) => module, - Err(error) => { - eprintln!("Unable to compile {input_path}."); - print_diagnostics(&db, &error.0); - std::process::exit(1) - } - }; + let compiled_module = match fe_driver::compile_ingot( + &mut db, + &build_files, + with_bytecode, + with_runtime_bytecode, + optimize, + ) { + Ok(module) => module, + Err(error) => { + eprintln!("Unable to compile {input_path}."); + print_diagnostics(&db, &error.0); + std::process::exit(1) + } + }; // no file content for ingots ("".to_string(), compiled_module) @@ -200,7 +209,18 @@ fn write_compiled_module( #[cfg(feature = "solc-backend")] if targets.contains(&Emit::Bytecode) { let file_name = format!("{}.bin", &name); - write_output(&contract_output_dir.join(file_name), &contract.bytecode)?; + write_output( + &contract_output_dir.join(file_name), + &contract.bytecode, + )?; + } + #[cfg(feature = "solc-backend")] + if targets.contains(&Emit::RuntimeBytecode) { + let file_name = format!("{}.runtime.bin", &name); + write_output( + &contract_output_dir.join(file_name), + &contract.runtime_bytecode, + )?; } } diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index d80e2b645c..22d3eb854a 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -341,6 +341,7 @@ pub fn deploy_contract( fixture, test_files::fixture(fixture), true, + false, true, ) { Ok(module) => module, @@ -376,7 +377,7 @@ pub fn deploy_contract_from_ingot( let files = test_files::fixture_dir_files("ingots"); let build_files = BuildFiles::load_static(files, path).expect("failed to load build files"); let mut db = driver::Db::default(); - let compiled_module = match driver::compile_ingot(&mut db, &build_files, true, true) { + let compiled_module = match driver::compile_ingot(&mut db, &build_files, true, false, true) { Ok(module) => module, Err(error) => { fe_common::diagnostics::print_diagnostics(&db, &error.0); @@ -587,12 +588,18 @@ pub fn compile_solidity_contract( #[allow(dead_code)] pub fn load_contract(address: H160, fixture: &str, contract_name: &str) -> ContractHarness { let mut db = driver::Db::default(); - let compiled_module = - driver::compile_single_file(&mut db, fixture, test_files::fixture(fixture), true, true) - .unwrap_or_else(|err| { - print_diagnostics(&db, &err.0); - panic!("failed to compile fixture: {fixture}"); - }); + let compiled_module = driver::compile_single_file( + &mut db, + fixture, + test_files::fixture(fixture), + true, + false, + true, + ) + .unwrap_or_else(|err| { + print_diagnostics(&db, &err.0); + panic!("failed to compile fixture: {fixture}"); + }); let compiled_contract = compiled_module .contracts .get(contract_name) @@ -712,9 +719,9 @@ impl ExecutionOutput { #[cfg(feature = "solc-backend")] fn execute_runtime_functions(executor: &mut Executor, runtime: &Runtime) -> (ExitReason, Vec) { let yul_code = runtime.to_yul().to_string().replace('"', "\\\""); - let bytecode = fe_yulc::compile_single_contract("Contract", &yul_code, false) + let contract_bytecode = fe_yulc::compile_single_contract("Contract", &yul_code, false, false) .expect("failed to compile Yul"); - let bytecode = hex::decode(bytecode).expect("failed to decode bytecode"); + let bytecode = hex::decode(contract_bytecode.bytecode).expect("failed to decode bytecode"); if let evm::Capture::Exit((reason, _, output)) = executor.create( address(DEFAULT_CALLER), diff --git a/crates/tests-legacy/src/crashes.rs b/crates/tests-legacy/src/crashes.rs index cd5de57760..fc3e291f5e 100644 --- a/crates/tests-legacy/src/crashes.rs +++ b/crates/tests-legacy/src/crashes.rs @@ -8,7 +8,7 @@ macro_rules! test_file { let mut db = fe_driver::Db::default(); let path = concat!("crashes/", stringify!($name), ".fe"); let src = test_files::fixture(path); - fe_driver::compile_single_file(&mut db, path, src, true, true).ok(); + fe_driver::compile_single_file(&mut db, path, src, true, false, true).ok(); } }; } diff --git a/crates/yulc/src/lib.rs b/crates/yulc/src/lib.rs index a98980f85f..b5c78ad0a9 100644 --- a/crates/yulc/src/lib.rs +++ b/crates/yulc/src/lib.rs @@ -3,16 +3,21 @@ use indexmap::map::IndexMap; #[derive(Debug)] pub struct YulcError(pub String); +pub struct ContractBytecode { + pub bytecode: String, + pub runtime_bytecode: String, +} + /// Compile a map of Yul contracts to a map of bytecode contracts. /// /// Returns a `contract_name -> hex_encoded_bytecode` map. pub fn compile( contracts: impl Iterator, impl AsRef)>, optimize: bool, -) -> Result, YulcError> { +) -> Result, YulcError> { contracts .map(|(name, yul_src)| { - compile_single_contract(name.as_ref(), yul_src.as_ref(), optimize) + compile_single_contract(name.as_ref(), yul_src.as_ref(), optimize, true) .map(|bytecode| (name.as_ref().to_string(), bytecode)) }) .collect() @@ -24,7 +29,8 @@ pub fn compile_single_contract( name: &str, yul_src: &str, optimize: bool, -) -> Result { + verify_runtime_bytecode: bool, +) -> Result { let solc_temp = include_str!("solc_temp.json"); let input = solc_temp .replace("{optimizer_enabled}", &optimize.to_string()) @@ -37,11 +43,22 @@ pub fn compile_single_contract( .to_string() .replace('"', ""); + let runtime_bytecode = output["contracts"]["input.yul"][name]["evm"]["deployedBytecode"] + ["object"] + .to_string() + .replace('"', ""); + if bytecode == "null" { return Err(YulcError(output.to_string())); } + if verify_runtime_bytecode && runtime_bytecode == "null" { + return Err(YulcError(output.to_string())); + } - Ok(bytecode) + Ok(ContractBytecode { + bytecode, + runtime_bytecode, + }) } #[cfg(not(feature = "solc-backend"))] @@ -50,7 +67,8 @@ pub fn compile_single_contract( _name: &str, _yul_src: &str, _optimize: bool, -) -> Result { + _verify_runtime_bytecode: bool, +) -> Result { // This is ugly, but required (as far as I can tell) to make // `cargo test --workspace` work without solc. panic!("fe-yulc requires 'solc-backend' feature") diff --git a/newsfragments/947.feature.md b/newsfragments/947.feature.md new file mode 100644 index 0000000000..f67137a493 --- /dev/null +++ b/newsfragments/947.feature.md @@ -0,0 +1,16 @@ +Give option to produce runtime bytecode as compilation artifact + +Previously, the compiler could only produce the bytecode that is used +for the deployment of the contract. Now it can also produce the runtime +bytecode which is the bytecode that is saved to storage. + +Being able to obtain the runtime bytecode is useful for contract +verification. + +To obtain the runtime bytecode use the `runtime-bytecode` option +of the `--emit` flag (multiple options allowed). + +Example Output: + +- mycontract.bin (bytecode for deployment) +- mycontract.runtime.bin (runtime bytecode)