mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-11-02 17:01:11 +00:00
Separate between compilation and linker phases to allow deploy time linking and back-porting era compiler changes to fix #91. Unlinked contract binaries (caused by missing libraries or missing factory dependencies in turn) are emitted as raw ELF object. Few drive by fixes: - #98 - A compiler panic on missing libraries definitions. - Fixes some incosistent type forwarding in JSON output (empty string vs. null object). - Remove the unused fallback for size optimization setting. - Remove the broken `--lvm-ir` mode. - CI workflow fixes. --------- Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com> Signed-off-by: xermicus <bigcyrill@hotmail.com> Signed-off-by: xermicus <cyrill@parity.io>
353 lines
12 KiB
Rust
353 lines
12 KiB
Rust
//! The Solidity project build.
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::io::Write;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
use normpath::PathExt;
|
|
|
|
use revive_common::ObjectFormat;
|
|
use revive_common::BYTE_LENGTH_ETH_ADDRESS;
|
|
use revive_llvm_context::polkavm_disassemble;
|
|
use revive_llvm_context::polkavm_hash;
|
|
use revive_llvm_context::polkavm_link;
|
|
use revive_llvm_context::DebugConfig;
|
|
use revive_solc_json_interface::combined_json::CombinedJson;
|
|
use revive_solc_json_interface::CombinedJsonContract;
|
|
use revive_solc_json_interface::SolcStandardJsonOutput;
|
|
use revive_solc_json_interface::SolcStandardJsonOutputContract;
|
|
use revive_solc_json_interface::SolcStandardJsonOutputError;
|
|
use revive_solc_json_interface::SolcStandardJsonOutputErrorHandler;
|
|
|
|
use crate::build::contract::Contract;
|
|
use crate::solc::version::Version as SolcVersion;
|
|
|
|
pub mod contract;
|
|
|
|
/// The Solidity project PVM build.
|
|
#[derive(Debug, Default)]
|
|
pub struct Build {
|
|
/// The contract data,
|
|
pub results: BTreeMap<String, Result<Contract, SolcStandardJsonOutputError>>,
|
|
/// The additional message to output (added by the revive compiler).
|
|
pub messages: Vec<SolcStandardJsonOutputError>,
|
|
}
|
|
|
|
impl Build {
|
|
/// A shorthand constructor.
|
|
///
|
|
/// Note: Takes the supplied `messages`, leaving an empty vec.
|
|
pub fn new(
|
|
results: BTreeMap<String, Result<Contract, SolcStandardJsonOutputError>>,
|
|
messages: &mut Vec<SolcStandardJsonOutputError>,
|
|
) -> Self {
|
|
Self {
|
|
results,
|
|
messages: std::mem::take(messages),
|
|
}
|
|
}
|
|
|
|
/// Links the PVM build.
|
|
pub fn link(
|
|
mut self,
|
|
linker_symbols: BTreeMap<String, [u8; BYTE_LENGTH_ETH_ADDRESS]>,
|
|
debug_config: &DebugConfig,
|
|
) -> Self {
|
|
let mut contracts: BTreeMap<String, Contract> = self
|
|
.results
|
|
.into_iter()
|
|
.map(|(path, result)| (path, result.expect("Cannot link a project with errors")))
|
|
.collect();
|
|
|
|
loop {
|
|
let mut linkage_data = BTreeMap::new();
|
|
for (path, contract) in contracts
|
|
.iter()
|
|
.filter(|(_path, contract)| contract.object_format == ObjectFormat::ELF)
|
|
{
|
|
match polkavm_link(
|
|
&contract.build.bytecode,
|
|
&linker_symbols,
|
|
&contract
|
|
.factory_dependencies
|
|
.iter()
|
|
.filter_map(|dependency| {
|
|
let bytecode_hash = contracts
|
|
.get(dependency)
|
|
.as_ref()?
|
|
.build
|
|
.bytecode_hash
|
|
.as_ref()?
|
|
.to_owned();
|
|
Some((dependency.to_owned(), bytecode_hash))
|
|
})
|
|
.collect(),
|
|
!debug_config.emit_debug_info,
|
|
) {
|
|
Ok((memory_buffer_linked, ObjectFormat::PVM)) => {
|
|
let bytecode_hash = polkavm_hash(&memory_buffer_linked);
|
|
let assembly_text =
|
|
polkavm_disassemble(path, &memory_buffer_linked, debug_config)
|
|
.unwrap_or_else(|error| {
|
|
panic!("ICE: The PVM disassembler failed: {error}")
|
|
});
|
|
linkage_data.insert(
|
|
path.to_owned(),
|
|
(memory_buffer_linked, bytecode_hash, assembly_text),
|
|
);
|
|
}
|
|
Ok((_memory_buffer_linked, ObjectFormat::ELF)) => {}
|
|
Err(error) => self
|
|
.messages
|
|
.push(SolcStandardJsonOutputError::new_error(error, None, None)),
|
|
}
|
|
}
|
|
if linkage_data.is_empty() {
|
|
break;
|
|
}
|
|
|
|
for (path, (memory_buffer_linked, bytecode_hash, assembly_text)) in
|
|
linkage_data.into_iter()
|
|
{
|
|
let contract = contracts.get(path.as_str()).expect("Always exists");
|
|
let factory_dependencies_resolved = contract
|
|
.factory_dependencies
|
|
.iter()
|
|
.filter_map(|dependency| {
|
|
Some((
|
|
contracts
|
|
.get(dependency)
|
|
.as_ref()?
|
|
.build
|
|
.bytecode_hash
|
|
.as_ref()?
|
|
.to_owned(),
|
|
dependency.to_owned(),
|
|
))
|
|
})
|
|
.collect();
|
|
let contract = contracts.get_mut(path.as_str()).expect("Always exists");
|
|
contract.build.bytecode = memory_buffer_linked.as_slice().to_vec();
|
|
contract.build.bytecode_hash = Some(bytecode_hash);
|
|
contract.build.assembly_text = Some(assembly_text);
|
|
contract.factory_dependencies_resolved = factory_dependencies_resolved;
|
|
contract.object_format = ObjectFormat::PVM;
|
|
}
|
|
}
|
|
|
|
let results = contracts
|
|
.into_iter()
|
|
.map(|(path, contract)| {
|
|
if contract.object_format == ObjectFormat::ELF {
|
|
self.messages.push(SolcStandardJsonOutputError::new_warning(
|
|
format!("{path} is unlinked. Consider providing missing libraries."),
|
|
None,
|
|
None,
|
|
));
|
|
}
|
|
(path, Ok(contract))
|
|
})
|
|
.collect();
|
|
Self::new(results, &mut self.messages)
|
|
}
|
|
|
|
/// Writes all contracts to the terminal.
|
|
pub fn write_to_terminal(
|
|
mut self,
|
|
output_metadata: bool,
|
|
output_assembly: bool,
|
|
output_binary: bool,
|
|
) -> anyhow::Result<()> {
|
|
self.take_and_write_warnings();
|
|
self.exit_on_error();
|
|
|
|
if !output_metadata && !output_assembly && !output_binary {
|
|
writeln!(
|
|
std::io::stderr(),
|
|
"Compiler run successful. No output requested. Use flags --metadata, --asm, --bin."
|
|
)?;
|
|
return Ok(());
|
|
}
|
|
|
|
for (path, build) in self.results.into_iter() {
|
|
build.expect("Always valid").write_to_terminal(
|
|
path,
|
|
output_metadata,
|
|
output_assembly,
|
|
output_binary,
|
|
)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Writes all contracts to the specified directory.
|
|
pub fn write_to_directory(
|
|
mut self,
|
|
output_directory: &Path,
|
|
output_metadata: bool,
|
|
output_assembly: bool,
|
|
output_binary: bool,
|
|
overwrite: bool,
|
|
) -> anyhow::Result<()> {
|
|
self.take_and_write_warnings();
|
|
self.exit_on_error();
|
|
|
|
std::fs::create_dir_all(output_directory)?;
|
|
|
|
for build in self.results.into_values() {
|
|
build.expect("Always valid").write_to_directory(
|
|
output_directory,
|
|
output_metadata,
|
|
output_assembly,
|
|
output_binary,
|
|
overwrite,
|
|
)?;
|
|
}
|
|
|
|
writeln!(
|
|
std::io::stderr(),
|
|
"Compiler run successful. Artifact(s) can be found in directory {output_directory:?}."
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Writes all contracts assembly and bytecode to the combined JSON.
|
|
pub fn write_to_combined_json(
|
|
mut self,
|
|
combined_json: &mut CombinedJson,
|
|
) -> anyhow::Result<()> {
|
|
self.take_and_write_warnings();
|
|
self.exit_on_error();
|
|
|
|
for result in self.results.into_values() {
|
|
let build = result.expect("Exits on an error above");
|
|
let identifier = build.identifier.clone();
|
|
|
|
let combined_json_contract =
|
|
match combined_json
|
|
.contracts
|
|
.iter_mut()
|
|
.find_map(|(json_path, contract)| {
|
|
if Self::normalize_full_path(identifier.full_path.as_str())
|
|
.ends_with(Self::normalize_full_path(json_path).as_str())
|
|
{
|
|
Some(contract)
|
|
} else {
|
|
None
|
|
}
|
|
}) {
|
|
Some(contract) => contract,
|
|
None => {
|
|
combined_json.contracts.insert(
|
|
identifier.full_path.clone(),
|
|
CombinedJsonContract::default(),
|
|
);
|
|
combined_json
|
|
.contracts
|
|
.get_mut(identifier.full_path.as_str())
|
|
.expect("Always exists")
|
|
}
|
|
};
|
|
|
|
build.write_to_combined_json(combined_json_contract)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Writes all contracts assembly and bytecode to the standard JSON.
|
|
pub fn write_to_standard_json(
|
|
self,
|
|
standard_json: &mut SolcStandardJsonOutput,
|
|
solc_version: &SolcVersion,
|
|
) -> anyhow::Result<()> {
|
|
let mut errors = Vec::with_capacity(self.results.len());
|
|
for result in self.results.into_values() {
|
|
let build = match result {
|
|
Ok(build) => build,
|
|
Err(error) => {
|
|
errors.push(error);
|
|
continue;
|
|
}
|
|
};
|
|
let identifier = build.identifier.clone();
|
|
|
|
match standard_json
|
|
.contracts
|
|
.get_mut(identifier.path.as_str())
|
|
.and_then(|contracts| {
|
|
contracts.get_mut(
|
|
identifier
|
|
.name
|
|
.as_deref()
|
|
.unwrap_or(identifier.path.as_str()),
|
|
)
|
|
}) {
|
|
Some(contract) => {
|
|
build.write_to_standard_json(contract)?;
|
|
}
|
|
None => {
|
|
let contracts = standard_json
|
|
.contracts
|
|
.entry(identifier.path.clone())
|
|
.or_default();
|
|
let mut contract = SolcStandardJsonOutputContract::default();
|
|
build.write_to_standard_json(&mut contract)?;
|
|
contracts.insert(identifier.name.unwrap_or(identifier.path), contract);
|
|
}
|
|
}
|
|
}
|
|
|
|
standard_json.errors.extend(errors);
|
|
standard_json.version = Some(solc_version.default.to_string());
|
|
standard_json.long_version = Some(solc_version.long.to_owned());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Normalizes the full contract path.
|
|
///
|
|
/// # Panics
|
|
/// If the path does not contain a colon.
|
|
fn normalize_full_path(path: &str) -> String {
|
|
let mut iterator = path.split(':');
|
|
let path = iterator.next().expect("Always exists");
|
|
let name = iterator.next().expect("Always exists");
|
|
|
|
let mut full_path = PathBuf::from(path)
|
|
.normalize()
|
|
.expect("Path normalization error")
|
|
.as_os_str()
|
|
.to_string_lossy()
|
|
.into_owned();
|
|
full_path.push(':');
|
|
full_path.push_str(name);
|
|
full_path
|
|
}
|
|
}
|
|
|
|
impl SolcStandardJsonOutputErrorHandler for Build {
|
|
fn errors(&self) -> Vec<&SolcStandardJsonOutputError> {
|
|
let mut errors: Vec<&SolcStandardJsonOutputError> = self
|
|
.results
|
|
.values()
|
|
.filter_map(|build| build.as_ref().err())
|
|
.collect();
|
|
errors.extend(self.messages.iter().filter(|message| message.is_error()));
|
|
errors
|
|
}
|
|
|
|
fn take_warnings(&mut self) -> Vec<SolcStandardJsonOutputError> {
|
|
let warnings = self
|
|
.messages
|
|
.iter()
|
|
.filter(|message| message.is_warning())
|
|
.cloned()
|
|
.collect();
|
|
self.messages.retain(|message| !message.is_warning());
|
|
warnings
|
|
}
|
|
}
|