Skip to content

Commit

Permalink
Include runtime_bytecode in artifacts
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Nov 1, 2023
1 parent 2c02c09 commit 162a75c
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 33 deletions.
42 changes: 33 additions & 9 deletions crates/driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -111,13 +113,14 @@ pub fn compile_single_file(
path: &str,
src: &str,
with_bytecode: bool,
with_runtime_bytecode: bool,
optimize: bool,
) -> Result<CompiledModule, CompileError> {
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))
}
Expand Down Expand Up @@ -158,6 +161,7 @@ pub fn compile_ingot(
db: &mut Db,
build_files: &BuildFiles,
with_bytecode: bool,
with_runtime_bytecode: bool,
optimize: bool,
) -> Result<CompiledModule, CompileError> {
let ingot = IngotId::from_build_files(db, build_files);
Expand All @@ -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")]
Expand Down Expand Up @@ -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)
}
Expand All @@ -239,6 +249,7 @@ fn compile_module(
db: &mut Db,
module_id: ModuleId,
with_bytecode: bool,
with_runtime_bytecode: bool,
optimize: bool,
) -> Result<CompiledModule, CompileError> {
let mut contracts = IndexMap::default();
Expand All @@ -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(
Expand All @@ -261,6 +278,7 @@ fn compile_module(
json_abi: serde_json::to_string_pretty(&abi).unwrap(),
yul: yul_contract,
bytecode,
runtime_bytecode,
},
);
}
Expand All @@ -277,6 +295,7 @@ fn compile_module(
db: &mut Db,
module_id: ModuleId,
_with_bytecode: bool,
_with_runtime_bytecode: bool,
_optimize: bool,
) -> Result<CompiledModule, CompileError> {
let mut contracts = IndexMap::default();
Expand Down Expand Up @@ -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::<Value>(&error.0)
Expand Down
35 changes: 26 additions & 9 deletions crates/fe/src/task/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum Emit {
Ast,
LoweredAst,
Bytecode,
RuntimeBytecode,
Tokens,
Yul,
}
Expand Down Expand Up @@ -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);

Expand All @@ -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,
Expand All @@ -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);

Expand All @@ -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)
Expand Down Expand Up @@ -202,6 +211,14 @@ fn write_compiled_module(
let file_name = format!("{}.bin", &name);
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,
)?;
}
}

Ok(())
Expand Down
25 changes: 16 additions & 9 deletions crates/test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ pub fn deploy_contract(
fixture,
test_files::fixture(fixture),
true,
false,
true,
) {
Ok(module) => module,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -712,9 +719,9 @@ impl ExecutionOutput {
#[cfg(feature = "solc-backend")]
fn execute_runtime_functions(executor: &mut Executor, runtime: &Runtime) -> (ExitReason, Vec<u8>) {
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),
Expand Down
2 changes: 1 addition & 1 deletion crates/tests-legacy/src/crashes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
};
}
Expand Down
28 changes: 23 additions & 5 deletions crates/yulc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = (impl AsRef<str>, impl AsRef<str>)>,
optimize: bool,
) -> Result<IndexMap<String, String>, YulcError> {
) -> Result<IndexMap<String, ContractBytecode>, 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()
Expand All @@ -24,7 +29,8 @@ pub fn compile_single_contract(
name: &str,
yul_src: &str,
optimize: bool,
) -> Result<String, YulcError> {
verify_runtime_bytecode: bool,
) -> Result<ContractBytecode, YulcError> {
let solc_temp = include_str!("solc_temp.json");
let input = solc_temp
.replace("{optimizer_enabled}", &optimize.to_string())
Expand All @@ -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"))]
Expand All @@ -50,7 +67,8 @@ pub fn compile_single_contract(
_name: &str,
_yul_src: &str,
_optimize: bool,
) -> Result<String, YulcError> {
_verify_runtime_bytecode: bool,
) -> Result<ContractBytecode, YulcError> {
// 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")
Expand Down
16 changes: 16 additions & 0 deletions newsfragments/947.feature.md
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 162a75c

Please sign in to comment.