mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-16 05:01:07 +00:00
Separate compilation and linker phases (#376)
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>
This commit is contained in:
@@ -1,21 +0,0 @@
|
||||
//! The contract LLVM IR source code.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The contract LLVM IR source code.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct LLVMIR {
|
||||
/// The LLVM IR file path.
|
||||
pub path: String,
|
||||
/// The LLVM IR source code.
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
impl LLVMIR {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(path: String, source: String) -> Self {
|
||||
Self { path, source }
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,40 @@
|
||||
//! The contract source code.
|
||||
|
||||
pub mod llvm_ir;
|
||||
pub mod yul;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use revive_yul::parser::statement::object::Object;
|
||||
|
||||
use self::llvm_ir::LLVMIR;
|
||||
use self::yul::Yul;
|
||||
|
||||
pub mod yul;
|
||||
|
||||
/// The contract source code.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum IR {
|
||||
/// The Yul source code.
|
||||
Yul(Yul),
|
||||
/// The LLVM IR source code.
|
||||
LLVMIR(LLVMIR),
|
||||
}
|
||||
|
||||
impl IR {
|
||||
/// A shortcut constructor.
|
||||
pub fn new_yul(source_code: String, object: Object) -> Self {
|
||||
Self::Yul(Yul::new(source_code, object))
|
||||
}
|
||||
|
||||
/// A shortcut constructor.
|
||||
pub fn new_llvm_ir(path: String, source: String) -> Self {
|
||||
Self::LLVMIR(LLVMIR::new(path, source))
|
||||
/// Drains the list of factory dependencies.
|
||||
pub fn drain_factory_dependencies(&mut self) -> BTreeSet<String> {
|
||||
match self {
|
||||
IR::Yul(ref mut yul) => yul.object.factory_dependencies.drain().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the list of missing deployable libraries.
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
pub fn get_missing_libraries(&self) -> BTreeSet<String> {
|
||||
match self {
|
||||
Self::Yul(inner) => inner.get_missing_libraries(),
|
||||
Self::LLVMIR(_inner) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for IR
|
||||
where
|
||||
D: revive_llvm_context::PolkaVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut revive_llvm_context::PolkaVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Self::Yul(inner) => inner.declare(context),
|
||||
Self::LLVMIR(_inner) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Self::Yul(inner) => inner.into_llvm(context),
|
||||
Self::LLVMIR(_inner) => Ok(()),
|
||||
}
|
||||
impl From<Yul> for IR {
|
||||
fn from(inner: Yul) -> Self {
|
||||
Self::Yul(inner)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,46 @@
|
||||
//! The contract Yul source code.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use revive_yul::lexer::Lexer;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use revive_yul::parser::statement::object::Object;
|
||||
|
||||
/// The contract Yul source code.
|
||||
/// he contract Yul source code.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Yul {
|
||||
/// The Yul source code.
|
||||
pub source_code: String,
|
||||
/// The Yul AST object.
|
||||
pub object: Object,
|
||||
}
|
||||
|
||||
impl Yul {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(source_code: String, object: Object) -> Self {
|
||||
Self {
|
||||
source_code,
|
||||
object,
|
||||
}
|
||||
/// Transforms the `solc` standard JSON output contract into a Yul object.
|
||||
pub fn try_from_source(source_code: &str) -> anyhow::Result<Option<Self>> {
|
||||
if source_code.is_empty() {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut lexer = Lexer::new(source_code.to_owned());
|
||||
let object = Object::parse(&mut lexer, None)
|
||||
.map_err(|error| anyhow::anyhow!("Yul parsing: {error:?}"))?;
|
||||
|
||||
Ok(Some(Self { object }))
|
||||
}
|
||||
|
||||
/// Get the list of missing deployable libraries.
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
pub fn get_missing_libraries(&self) -> BTreeSet<String> {
|
||||
self.object.get_missing_libraries()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for Yul
|
||||
where
|
||||
D: revive_llvm_context::PolkaVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut revive_llvm_context::PolkaVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
impl revive_llvm_context::PolkaVMWriteLLVM for Yul {
|
||||
fn declare(&mut self, context: &mut revive_llvm_context::PolkaVMContext) -> anyhow::Result<()> {
|
||||
self.object.declare(context)
|
||||
}
|
||||
|
||||
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
|
||||
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext) -> anyhow::Result<()> {
|
||||
self.object.into_llvm(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! The Solidity contract metadata.
|
||||
|
||||
use revive_llvm_context::OptimizerSettings;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ResolcVersion;
|
||||
@@ -11,13 +12,11 @@ pub struct Metadata {
|
||||
/// The `solc` metadata.
|
||||
pub solc_metadata: serde_json::Value,
|
||||
/// The `solc` version.
|
||||
pub solc_version: String,
|
||||
pub solc_version: Option<semver::Version>,
|
||||
/// The pallet revive edition.
|
||||
pub revive_pallet_version: Option<semver::Version>,
|
||||
/// The PolkaVM compiler version.
|
||||
pub revive_version: String,
|
||||
/// The PolkaVM compiler optimizer settings.
|
||||
pub optimizer_settings: revive_llvm_context::OptimizerSettings,
|
||||
pub optimizer_settings: OptimizerSettings,
|
||||
/// The extra LLVM arguments give used for manual control.
|
||||
pub llvm_arguments: Vec<String>,
|
||||
}
|
||||
@@ -26,15 +25,13 @@ impl Metadata {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
solc_metadata: serde_json::Value,
|
||||
solc_version: String,
|
||||
revive_pallet_version: Option<semver::Version>,
|
||||
optimizer_settings: revive_llvm_context::OptimizerSettings,
|
||||
solc_version: Option<semver::Version>,
|
||||
optimizer_settings: OptimizerSettings,
|
||||
llvm_arguments: Vec<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
solc_metadata,
|
||||
solc_version,
|
||||
revive_pallet_version,
|
||||
revive_version: ResolcVersion::default().long,
|
||||
optimizer_settings,
|
||||
llvm_arguments,
|
||||
|
||||
@@ -1,29 +1,38 @@
|
||||
//! The contract data.
|
||||
|
||||
pub mod ir;
|
||||
pub mod metadata;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use revive_common::ContractIdentifier;
|
||||
use revive_common::Keccak256;
|
||||
use revive_common::MetadataHash;
|
||||
use revive_common::ObjectFormat;
|
||||
use revive_llvm_context::DebugConfig;
|
||||
use revive_llvm_context::Optimizer;
|
||||
use revive_llvm_context::OptimizerSettings;
|
||||
use revive_llvm_context::PolkaVMContext;
|
||||
use revive_llvm_context::PolkaVMContextSolidityData;
|
||||
use revive_llvm_context::PolkaVMContextYulData;
|
||||
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha3::Digest;
|
||||
|
||||
use revive_llvm_context::PolkaVMWriteLLVM;
|
||||
|
||||
use crate::build::contract::Contract as ContractBuild;
|
||||
use crate::project::Project;
|
||||
use crate::solc::version::Version as SolcVersion;
|
||||
|
||||
use self::ir::IR;
|
||||
use self::metadata::Metadata;
|
||||
|
||||
pub mod ir;
|
||||
pub mod metadata;
|
||||
|
||||
/// The contract data.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Contract {
|
||||
/// The absolute file path.
|
||||
pub path: String,
|
||||
pub identifier: ContractIdentifier,
|
||||
/// The IR source code data.
|
||||
pub ir: IR,
|
||||
/// The metadata JSON.
|
||||
@@ -32,22 +41,9 @@ pub struct Contract {
|
||||
|
||||
impl Contract {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
path: String,
|
||||
source_hash: [u8; revive_common::BYTE_LENGTH_WORD],
|
||||
source_version: SolcVersion,
|
||||
ir: IR,
|
||||
metadata_json: Option<serde_json::Value>,
|
||||
) -> Self {
|
||||
let metadata_json = metadata_json.unwrap_or_else(|| {
|
||||
serde_json::json!({
|
||||
"source_hash": hex::encode(source_hash.as_slice()),
|
||||
"source_version": serde_json::to_value(&source_version).expect("Always valid"),
|
||||
})
|
||||
});
|
||||
|
||||
pub fn new(identifier: ContractIdentifier, ir: IR, metadata_json: serde_json::Value) -> Self {
|
||||
Self {
|
||||
path,
|
||||
identifier,
|
||||
ir,
|
||||
metadata_json,
|
||||
}
|
||||
@@ -56,136 +52,77 @@ impl Contract {
|
||||
/// Returns the contract identifier, which is:
|
||||
/// - the Yul object identifier for Yul
|
||||
/// - the module name for LLVM IR
|
||||
pub fn identifier(&self) -> &str {
|
||||
pub fn object_identifier(&self) -> &str {
|
||||
match self.ir {
|
||||
IR::Yul(ref yul) => yul.object.identifier.as_str(),
|
||||
IR::LLVMIR(ref llvm_ir) => llvm_ir.path.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract factory dependencies.
|
||||
pub fn drain_factory_dependencies(&mut self) -> HashSet<String> {
|
||||
match self.ir {
|
||||
IR::Yul(ref mut yul) => yul.object.factory_dependencies.drain().collect(),
|
||||
IR::LLVMIR(_) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiles the specified contract, setting its build artifacts.
|
||||
pub fn compile(
|
||||
mut self,
|
||||
project: Project,
|
||||
optimizer_settings: revive_llvm_context::OptimizerSettings,
|
||||
include_metadata_hash: bool,
|
||||
mut debug_config: revive_llvm_context::DebugConfig,
|
||||
self,
|
||||
solc_version: Option<SolcVersion>,
|
||||
optimizer_settings: OptimizerSettings,
|
||||
metadata_hash: MetadataHash,
|
||||
mut debug_config: DebugConfig,
|
||||
llvm_arguments: &[String],
|
||||
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
|
||||
missing_libraries: BTreeSet<String>,
|
||||
factory_dependencies: BTreeSet<String>,
|
||||
identifier_paths: BTreeMap<String, String>,
|
||||
) -> anyhow::Result<ContractBuild> {
|
||||
let llvm = inkwell::context::Context::create();
|
||||
let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings);
|
||||
|
||||
let version = project.version.clone();
|
||||
let identifier = self.identifier().to_owned();
|
||||
|
||||
let optimizer = Optimizer::new(optimizer_settings);
|
||||
let metadata = Metadata::new(
|
||||
self.metadata_json.take(),
|
||||
version.long.clone(),
|
||||
version.l2_revision.clone(),
|
||||
self.metadata_json,
|
||||
solc_version
|
||||
.as_ref()
|
||||
.map(|version| version.default.to_owned()),
|
||||
optimizer.settings().to_owned(),
|
||||
llvm_arguments.to_vec(),
|
||||
llvm_arguments.to_owned(),
|
||||
);
|
||||
let metadata_json = serde_json::to_value(&metadata).expect("Always valid");
|
||||
let metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]> = if include_metadata_hash
|
||||
{
|
||||
let metadata_string = serde_json::to_string(&metadata).expect("Always valid");
|
||||
Some(sha3::Keccak256::digest(metadata_string.as_bytes()).into())
|
||||
} else {
|
||||
None
|
||||
let metadata_json_bytes = serde_json::to_vec(&metadata_json).expect("Always valid");
|
||||
let metadata_bytes = match metadata_hash {
|
||||
MetadataHash::Keccak256 => Keccak256::from_slice(&metadata_json_bytes).into(),
|
||||
MetadataHash::IPFS => todo!("IPFS hash isn't supported yet"),
|
||||
MetadataHash::None => None,
|
||||
};
|
||||
debug_config.set_contract_path(&self.identifier.full_path);
|
||||
|
||||
let module = match self.ir {
|
||||
IR::LLVMIR(ref llvm_ir) => {
|
||||
// Create the output module
|
||||
let memory_buffer =
|
||||
inkwell::memory_buffer::MemoryBuffer::create_from_memory_range_copy(
|
||||
llvm_ir.source.as_bytes(),
|
||||
self.path.as_str(),
|
||||
);
|
||||
llvm.create_module_from_ir(memory_buffer)
|
||||
.map_err(|error| anyhow::anyhow!(error.to_string()))?
|
||||
let build = match self.ir {
|
||||
IR::Yul(mut yul) => {
|
||||
let module = llvm.create_module(self.identifier.full_path.as_str());
|
||||
let mut context =
|
||||
PolkaVMContext::new(&llvm, module, optimizer, debug_config, memory_config);
|
||||
context.set_solidity_data(PolkaVMContextSolidityData::default());
|
||||
let yul_data = PolkaVMContextYulData::new(identifier_paths);
|
||||
context.set_yul_data(yul_data);
|
||||
|
||||
yul.declare(&mut context)?;
|
||||
yul.into_llvm(&mut context)
|
||||
.map_err(|error| anyhow::anyhow!("LLVM IR generator: {error}"))?;
|
||||
|
||||
context.build(self.identifier.full_path.as_str(), metadata_bytes)?
|
||||
}
|
||||
_ => llvm.create_module(self.path.as_str()),
|
||||
};
|
||||
|
||||
debug_config.set_contract_path(&self.path);
|
||||
let mut context = revive_llvm_context::PolkaVMContext::new(
|
||||
&llvm,
|
||||
module,
|
||||
optimizer,
|
||||
Some(project),
|
||||
include_metadata_hash,
|
||||
debug_config,
|
||||
llvm_arguments,
|
||||
memory_config,
|
||||
);
|
||||
context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default());
|
||||
match self.ir {
|
||||
IR::Yul(_) => {
|
||||
context.set_yul_data(Default::default());
|
||||
}
|
||||
IR::LLVMIR(_) => {}
|
||||
}
|
||||
|
||||
let factory_dependencies = self.drain_factory_dependencies();
|
||||
|
||||
self.ir.declare(&mut context).map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"The contract `{}` LLVM IR generator declaration pass error: {}",
|
||||
self.path,
|
||||
error
|
||||
)
|
||||
})?;
|
||||
self.ir.into_llvm(&mut context).map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"The contract `{}` LLVM IR generator definition pass error: {}",
|
||||
self.path,
|
||||
error
|
||||
)
|
||||
})?;
|
||||
|
||||
if let Some(debug_info) = context.debug_info() {
|
||||
debug_info.finalize_module()
|
||||
}
|
||||
|
||||
let build = context.build(self.path.as_str(), metadata_hash)?;
|
||||
|
||||
Ok(ContractBuild::new(
|
||||
self.path,
|
||||
identifier,
|
||||
self.identifier,
|
||||
build,
|
||||
metadata_json,
|
||||
missing_libraries,
|
||||
factory_dependencies,
|
||||
ObjectFormat::ELF,
|
||||
))
|
||||
}
|
||||
|
||||
/// Get the list of missing deployable libraries.
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
self.ir.get_missing_libraries()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> PolkaVMWriteLLVM<D> for Contract
|
||||
where
|
||||
D: revive_llvm_context::PolkaVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut revive_llvm_context::PolkaVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.ir.declare(context)
|
||||
}
|
||||
|
||||
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
|
||||
self.ir.into_llvm(context)
|
||||
pub fn get_missing_libraries(&self, deployed_libraries: &BTreeSet<String>) -> BTreeSet<String> {
|
||||
self.ir
|
||||
.get_missing_libraries()
|
||||
.into_iter()
|
||||
.filter(|library| !deployed_libraries.contains(library))
|
||||
.collect::<BTreeSet<String>>()
|
||||
}
|
||||
}
|
||||
|
||||
+163
-282
@@ -3,54 +3,59 @@
|
||||
pub mod contract;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use revive_common::Keccak256;
|
||||
use revive_common::MetadataHash;
|
||||
use revive_llvm_context::DebugConfig;
|
||||
use revive_llvm_context::OptimizerSettings;
|
||||
use revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory;
|
||||
use revive_solc_json_interface::SolcStandardJsonInputSource;
|
||||
use revive_solc_json_interface::SolcStandardJsonOutputError;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha3::Digest;
|
||||
|
||||
use revive_common::ContractIdentifier;
|
||||
use revive_solc_json_interface::SolcStandardJsonInputSettingsLibraries;
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
use revive_yul::lexer::Lexer;
|
||||
use revive_yul::parser::statement::object::Object;
|
||||
|
||||
use crate::build::contract::Contract as ContractBuild;
|
||||
use crate::build::Build;
|
||||
use crate::missing_libraries::MissingLibraries;
|
||||
use crate::process::input::Input as ProcessInput;
|
||||
use crate::process::Process;
|
||||
use crate::project::contract::ir::yul::Yul;
|
||||
use crate::project::contract::ir::IR;
|
||||
use crate::project::contract::Contract;
|
||||
use crate::solc::version::Version as SolcVersion;
|
||||
use crate::solc::Compiler;
|
||||
|
||||
use self::contract::Contract;
|
||||
use crate::ProcessOutput;
|
||||
|
||||
/// The processes input data.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Project {
|
||||
/// The source code version.
|
||||
pub version: SolcVersion,
|
||||
pub version: Option<SolcVersion>,
|
||||
/// The project contracts,
|
||||
pub contracts: BTreeMap<String, Contract>,
|
||||
/// The mapping of auxiliary identifiers, e.g. Yul object names, to full contract paths.
|
||||
pub identifier_paths: BTreeMap<String, String>,
|
||||
/// The library addresses.
|
||||
pub libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
pub libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
version: SolcVersion,
|
||||
version: Option<SolcVersion>,
|
||||
contracts: BTreeMap<String, Contract>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
) -> Self {
|
||||
let mut identifier_paths = BTreeMap::new();
|
||||
for (path, contract) in contracts.iter() {
|
||||
identifier_paths.insert(contract.identifier().to_owned(), path.to_owned());
|
||||
identifier_paths.insert(contract.object_identifier().to_owned(), path.to_owned());
|
||||
}
|
||||
|
||||
Self {
|
||||
@@ -64,319 +69,195 @@ impl Project {
|
||||
/// Compiles all contracts, returning their build artifacts.
|
||||
pub fn compile(
|
||||
self,
|
||||
optimizer_settings: revive_llvm_context::OptimizerSettings,
|
||||
include_metadata_hash: bool,
|
||||
debug_config: revive_llvm_context::DebugConfig,
|
||||
messages: &mut Vec<SolcStandardJsonOutputError>,
|
||||
optimizer_settings: OptimizerSettings,
|
||||
metadata_hash: MetadataHash,
|
||||
debug_config: &DebugConfig,
|
||||
llvm_arguments: &[String],
|
||||
memory_config: revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory,
|
||||
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
|
||||
) -> anyhow::Result<Build> {
|
||||
let project = self.clone();
|
||||
let deployed_libraries = self.libraries.as_paths();
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
let iter = self.contracts.into_par_iter();
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let iter = self.contracts.into_iter();
|
||||
|
||||
let results: BTreeMap<String, anyhow::Result<ContractBuild>> = iter
|
||||
.map(|(full_path, contract)| {
|
||||
let process_input = ProcessInput::new(
|
||||
let results = iter
|
||||
.map(|(path, mut contract)| {
|
||||
let factory_dependencies = contract
|
||||
.ir
|
||||
.drain_factory_dependencies()
|
||||
.iter()
|
||||
.map(|identifier| {
|
||||
self.identifier_paths
|
||||
.get(identifier)
|
||||
.cloned()
|
||||
.expect("Always exists")
|
||||
})
|
||||
.collect();
|
||||
let missing_libraries = contract.get_missing_libraries(&deployed_libraries);
|
||||
let input = ProcessInput::new(
|
||||
contract,
|
||||
project.clone(),
|
||||
include_metadata_hash,
|
||||
self.version.clone(),
|
||||
metadata_hash,
|
||||
optimizer_settings.clone(),
|
||||
debug_config.clone(),
|
||||
llvm_arguments.to_vec(),
|
||||
llvm_arguments.to_owned(),
|
||||
memory_config,
|
||||
missing_libraries,
|
||||
factory_dependencies,
|
||||
self.identifier_paths.clone(),
|
||||
);
|
||||
let process_output = {
|
||||
let result: Result<ProcessOutput, SolcStandardJsonOutputError> = {
|
||||
#[cfg(target_os = "emscripten")]
|
||||
{
|
||||
crate::WorkerProcess::call(process_input)
|
||||
crate::WorkerProcess::call(path.as_str(), input)
|
||||
}
|
||||
#[cfg(not(target_os = "emscripten"))]
|
||||
{
|
||||
crate::NativeProcess::call(process_input)
|
||||
crate::NativeProcess::call(path.as_str(), input)
|
||||
}
|
||||
};
|
||||
(full_path, process_output.map(|output| output.build))
|
||||
let result = result.map(|output| output.build);
|
||||
(path, result)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut build = Build::default();
|
||||
let mut hashes = HashMap::with_capacity(results.len());
|
||||
for (path, result) in results.iter() {
|
||||
match result {
|
||||
Ok(contract) => {
|
||||
hashes.insert(path.to_owned(), contract.build.bytecode_hash.to_owned());
|
||||
}
|
||||
Err(error) => {
|
||||
anyhow::bail!("Contract `{}` compiling error: {:?}", path, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (path, result) in results.into_iter() {
|
||||
match result {
|
||||
Ok(mut contract) => {
|
||||
for dependency in contract.factory_dependencies.drain() {
|
||||
let dependency_path = project
|
||||
.identifier_paths
|
||||
.get(dependency.as_str())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Dependency `{dependency}` full path not found")
|
||||
});
|
||||
let hash = match hashes.get(dependency_path.as_str()) {
|
||||
Some(hash) => hash.to_owned(),
|
||||
None => anyhow::bail!(
|
||||
"Dependency contract `{}` not found in the project",
|
||||
dependency_path
|
||||
),
|
||||
};
|
||||
contract
|
||||
.build
|
||||
.factory_dependencies
|
||||
.insert(hash, dependency_path);
|
||||
}
|
||||
|
||||
build.contracts.insert(path, contract);
|
||||
}
|
||||
Err(error) => {
|
||||
anyhow::bail!("Contract `{}` compiling error: {:?}", path, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(build)
|
||||
.collect::<BTreeMap<String, Result<ContractBuild, SolcStandardJsonOutputError>>>();
|
||||
Ok(Build::new(results, messages))
|
||||
}
|
||||
|
||||
/// Get the list of missing deployable libraries.
|
||||
pub fn get_missing_libraries(&self) -> MissingLibraries {
|
||||
let deployed_libraries = self
|
||||
.libraries
|
||||
pub fn get_missing_libraries(&self, deployed_libraries: &BTreeSet<String>) -> MissingLibraries {
|
||||
let missing_libraries = self
|
||||
.contracts
|
||||
.iter()
|
||||
.flat_map(|(file, names)| {
|
||||
names
|
||||
.keys()
|
||||
.map(|name| format!("{file}:{name}"))
|
||||
.collect::<HashSet<String>>()
|
||||
.map(|(path, contract)| {
|
||||
(
|
||||
path.to_owned(),
|
||||
contract.get_missing_libraries(deployed_libraries),
|
||||
)
|
||||
})
|
||||
.collect::<HashSet<String>>();
|
||||
|
||||
let mut missing_deployable_libraries = BTreeMap::new();
|
||||
for (contract_path, contract) in self.contracts.iter() {
|
||||
let missing_libraries = contract
|
||||
.get_missing_libraries()
|
||||
.into_iter()
|
||||
.filter(|library| !deployed_libraries.contains(library))
|
||||
.collect::<HashSet<String>>();
|
||||
missing_deployable_libraries.insert(contract_path.to_owned(), missing_libraries);
|
||||
}
|
||||
MissingLibraries::new(missing_deployable_libraries)
|
||||
.collect();
|
||||
MissingLibraries::new(missing_libraries)
|
||||
}
|
||||
|
||||
/// Parses the Yul source code file and returns the source data.
|
||||
pub fn try_from_yul_path<T: Compiler>(
|
||||
path: &Path,
|
||||
solc_validator: Option<&T>,
|
||||
pub fn try_from_yul_paths(
|
||||
paths: &[PathBuf],
|
||||
solc_output: Option<&mut SolcStandardJsonOutput>,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
debug_config: &DebugConfig,
|
||||
) -> anyhow::Result<Self> {
|
||||
let source_code = std::fs::read_to_string(path)
|
||||
.map_err(|error| anyhow::anyhow!("Yul file {:?} reading error: {}", path, error))?;
|
||||
Self::try_from_yul_string(path, source_code.as_str(), solc_validator)
|
||||
let sources = paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let source = SolcStandardJsonInputSource::from(path.as_path());
|
||||
(path.to_string_lossy().to_string(), source)
|
||||
})
|
||||
.collect::<BTreeMap<String, SolcStandardJsonInputSource>>();
|
||||
Self::try_from_yul_sources(sources, libraries, solc_output, debug_config)
|
||||
}
|
||||
|
||||
/// Parses the test Yul source code string and returns the source data.
|
||||
/// Only for integration testing purposes.
|
||||
pub fn try_from_yul_string<T: Compiler>(
|
||||
path: &Path,
|
||||
source_code: &str,
|
||||
solc_validator: Option<&T>,
|
||||
pub fn try_from_yul_sources(
|
||||
sources: BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
mut solc_output: Option<&mut SolcStandardJsonOutput>,
|
||||
debug_config: &DebugConfig,
|
||||
) -> anyhow::Result<Self> {
|
||||
if let Some(solc) = solc_validator {
|
||||
solc.validate_yul(path)?;
|
||||
#[cfg(feature = "parallel")]
|
||||
let iter = sources.into_par_iter();
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let iter = sources.into_iter();
|
||||
|
||||
let results = iter
|
||||
.filter_map(|(path, mut source)| {
|
||||
let source_code = match source.try_resolve() {
|
||||
Ok(()) => source.take_content().expect("Always exists"),
|
||||
Err(error) => return Some((path, Err(error))),
|
||||
};
|
||||
let ir = match Yul::try_from_source(&source_code) {
|
||||
Ok(ir) => ir?,
|
||||
Err(error) => return Some((path, Err(error))),
|
||||
};
|
||||
let object_identifier = ir.object.identifier.clone();
|
||||
let name = ContractIdentifier::new(path.clone(), Some(object_identifier));
|
||||
let full_path = name.full_path.clone();
|
||||
if let Err(error) = debug_config.dump_yul(&name.full_path, &source_code) {
|
||||
return Some((full_path.clone(), Err(error)));
|
||||
}
|
||||
let source_metadata = serde_json::json!({
|
||||
"source_hash": Keccak256::from_slice(source_code.as_bytes()).to_string()
|
||||
});
|
||||
let contract = Contract::new(name, ir.into(), source_metadata);
|
||||
Some((full_path, Ok(contract)))
|
||||
})
|
||||
.collect::<BTreeMap<String, anyhow::Result<Contract>>>();
|
||||
|
||||
let mut contracts = BTreeMap::new();
|
||||
for (path, result) in results.into_iter() {
|
||||
match result {
|
||||
Ok(contract) => {
|
||||
contracts.insert(path, contract);
|
||||
}
|
||||
Err(error) => match solc_output {
|
||||
Some(ref mut solc_output) => solc_output.push_error(Some(path), error),
|
||||
None => anyhow::bail!(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let source_version = SolcVersion::new_simple(crate::solc::LAST_SUPPORTED_VERSION);
|
||||
let path = path.to_string_lossy().to_string();
|
||||
let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into();
|
||||
|
||||
let mut lexer = Lexer::new(source_code.to_owned());
|
||||
let object = Object::parse(&mut lexer, None)
|
||||
.map_err(|error| anyhow::anyhow!("Yul object `{}` parsing error: {}", path, error))?;
|
||||
|
||||
let mut project_contracts = BTreeMap::new();
|
||||
project_contracts.insert(
|
||||
path.to_owned(),
|
||||
Contract::new(
|
||||
path,
|
||||
source_hash,
|
||||
source_version.clone(),
|
||||
IR::new_yul(source_code.to_owned(), object),
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
Ok(Self::new(
|
||||
source_version,
|
||||
project_contracts,
|
||||
BTreeMap::new(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Parses the LLVM IR source code file and returns the source data.
|
||||
pub fn try_from_llvm_ir_path(path: &Path) -> anyhow::Result<Self> {
|
||||
let source_code = std::fs::read_to_string(path)
|
||||
.map_err(|error| anyhow::anyhow!("LLVM IR file {:?} reading error: {}", path, error))?;
|
||||
let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into();
|
||||
|
||||
let source_version =
|
||||
SolcVersion::new_simple(revive_llvm_context::polkavm_const::LLVM_VERSION);
|
||||
let path = path.to_string_lossy().to_string();
|
||||
|
||||
let mut project_contracts = BTreeMap::new();
|
||||
project_contracts.insert(
|
||||
path.clone(),
|
||||
Contract::new(
|
||||
path.clone(),
|
||||
source_hash,
|
||||
source_version.clone(),
|
||||
IR::new_llvm_ir(path, source_code),
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
Ok(Self::new(
|
||||
source_version,
|
||||
project_contracts,
|
||||
BTreeMap::new(),
|
||||
))
|
||||
Ok(Self::new(None, contracts, libraries))
|
||||
}
|
||||
|
||||
/// Converts the `solc` JSON output into a convenient project.
|
||||
pub fn try_from_standard_json_output(
|
||||
output: &SolcStandardJsonOutput,
|
||||
source_code_files: BTreeMap<String, String>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
solc_output: &mut SolcStandardJsonOutput,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
solc_version: &SolcVersion,
|
||||
debug_config: &revive_llvm_context::DebugConfig,
|
||||
debug_config: &DebugConfig,
|
||||
) -> anyhow::Result<Self> {
|
||||
let files = match output.contracts.as_ref() {
|
||||
Some(files) => files,
|
||||
None => match &output.errors {
|
||||
Some(errors) if errors.iter().any(|e| e.severity == "error") => {
|
||||
anyhow::bail!(serde_json::to_string_pretty(errors).expect("Always valid"));
|
||||
}
|
||||
_ => &BTreeMap::new(),
|
||||
},
|
||||
};
|
||||
let mut project_contracts = BTreeMap::new();
|
||||
|
||||
for (path, contracts) in files.iter() {
|
||||
for (name, contract) in contracts.iter() {
|
||||
let full_path = format!("{path}:{name}");
|
||||
|
||||
let ir_optimized = match contract.ir_optimized.to_owned() {
|
||||
Some(ir_optimized) => ir_optimized,
|
||||
None => continue,
|
||||
};
|
||||
if ir_optimized.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
debug_config.dump_yul(full_path.as_str(), ir_optimized.as_str())?;
|
||||
|
||||
let mut lexer = Lexer::new(ir_optimized.to_owned());
|
||||
let object = Object::parse(&mut lexer, None).map_err(|error| {
|
||||
anyhow::anyhow!("Contract `{}` parsing error: {:?}", full_path, error)
|
||||
})?;
|
||||
|
||||
let source = IR::new_yul(ir_optimized.to_owned(), object);
|
||||
|
||||
let source_code = source_code_files
|
||||
.get(path.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Source code for path `{}` not found", path))?;
|
||||
let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into();
|
||||
|
||||
let project_contract = Contract::new(
|
||||
full_path.clone(),
|
||||
source_hash,
|
||||
solc_version.to_owned(),
|
||||
source,
|
||||
contract.metadata.to_owned(),
|
||||
);
|
||||
project_contracts.insert(full_path, project_contract);
|
||||
let mut input_contracts = Vec::with_capacity(solc_output.contracts.len());
|
||||
for (path, file) in solc_output.contracts.iter() {
|
||||
for (name, contract) in file.iter() {
|
||||
let name = ContractIdentifier::new((*path).to_owned(), Some((*name).to_owned()));
|
||||
input_contracts.push((name, contract));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
let iter = input_contracts.into_par_iter();
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let iter = input_contracts.into_iter();
|
||||
|
||||
let results = iter
|
||||
.filter_map(|(name, contract)| {
|
||||
let ir = match Yul::try_from_source(&contract.ir_optimized)
|
||||
.map(|yul| yul.map(IR::from))
|
||||
{
|
||||
Ok(ir) => ir?,
|
||||
Err(error) => return Some((name.full_path, Err(error))),
|
||||
};
|
||||
if let Err(error) = debug_config.dump_yul(&name.full_path, &contract.ir_optimized) {
|
||||
return Some((name.full_path, Err(error)));
|
||||
}
|
||||
let contract = Contract::new(name.clone(), ir, contract.metadata.clone());
|
||||
Some((name.full_path, Ok(contract)))
|
||||
})
|
||||
.collect::<BTreeMap<String, anyhow::Result<Contract>>>();
|
||||
|
||||
let mut contracts = BTreeMap::new();
|
||||
for (path, result) in results.into_iter() {
|
||||
match result {
|
||||
Ok(contract) => {
|
||||
contracts.insert(path, contract);
|
||||
}
|
||||
Err(error) => solc_output.push_error(Some(path), error),
|
||||
}
|
||||
}
|
||||
Ok(Project::new(
|
||||
solc_version.to_owned(),
|
||||
project_contracts,
|
||||
Some(solc_version.clone()),
|
||||
contracts,
|
||||
libraries,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl revive_llvm_context::PolkaVMDependency for Project {
|
||||
fn compile(
|
||||
project: Self,
|
||||
identifier: &str,
|
||||
optimizer_settings: revive_llvm_context::OptimizerSettings,
|
||||
include_metadata_hash: bool,
|
||||
debug_config: revive_llvm_context::DebugConfig,
|
||||
llvm_arguments: &[String],
|
||||
memory_config: revive_solc_json_interface::SolcStandardJsonInputSettingsPolkaVMMemory,
|
||||
) -> anyhow::Result<String> {
|
||||
let contract_path = project.resolve_path(identifier)?;
|
||||
let contract = project
|
||||
.contracts
|
||||
.get(contract_path.as_str())
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Dependency contract `{}` not found in the project",
|
||||
contract_path
|
||||
)
|
||||
})?;
|
||||
|
||||
contract
|
||||
.compile(
|
||||
project,
|
||||
optimizer_settings,
|
||||
include_metadata_hash,
|
||||
debug_config,
|
||||
llvm_arguments,
|
||||
memory_config,
|
||||
)
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"Dependency contract `{}` compiling error: {}",
|
||||
identifier,
|
||||
error
|
||||
)
|
||||
})
|
||||
.map(|contract| contract.build.bytecode_hash)
|
||||
}
|
||||
|
||||
fn resolve_path(&self, identifier: &str) -> anyhow::Result<String> {
|
||||
self.identifier_paths
|
||||
.get(identifier.strip_suffix("_deployed").unwrap_or(identifier))
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Contract with identifier `{}` not found in the project",
|
||||
identifier
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_library(&self, path: &str) -> anyhow::Result<String> {
|
||||
for (file_path, contracts) in self.libraries.iter() {
|
||||
for (contract_name, address) in contracts.iter() {
|
||||
let key = format!("{file_path}:{contract_name}");
|
||||
if key.as_str() == path {
|
||||
return Ok(address["0x".len()..].to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::bail!("Library `{}` not found in the project", path);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user