mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-04-24 04:17:58 +00:00
Emerge Yul recompiler (#1)
Provide a modified (and incomplete) version of ZKSync zksolc that can compile the most basic contracts
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
//!
|
||||
//! The Solidity contract build.
|
||||
//!
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::solc::combined_json::contract::Contract as CombinedJsonContract;
|
||||
use crate::solc::standard_json::output::contract::Contract as StandardJsonOutputContract;
|
||||
|
||||
///
|
||||
/// The Solidity contract build.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Contract {
|
||||
/// The contract path.
|
||||
pub path: String,
|
||||
/// The auxiliary identifier. Used to identify Yul objects.
|
||||
pub identifier: String,
|
||||
/// The LLVM module build.
|
||||
pub build: era_compiler_llvm_context::EraVMBuild,
|
||||
/// The metadata JSON.
|
||||
pub metadata_json: serde_json::Value,
|
||||
/// The factory dependencies.
|
||||
pub factory_dependencies: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Contract {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
path: String,
|
||||
identifier: String,
|
||||
build: era_compiler_llvm_context::EraVMBuild,
|
||||
metadata_json: serde_json::Value,
|
||||
factory_dependencies: HashSet<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path,
|
||||
identifier,
|
||||
build,
|
||||
metadata_json,
|
||||
factory_dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Writes the contract text assembly and bytecode to files.
|
||||
///
|
||||
pub fn write_to_directory(
|
||||
self,
|
||||
path: &Path,
|
||||
output_assembly: bool,
|
||||
output_binary: bool,
|
||||
overwrite: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let file_name = Self::short_path(self.path.as_str());
|
||||
|
||||
if output_assembly {
|
||||
let file_name = format!(
|
||||
"{}.{}",
|
||||
file_name,
|
||||
era_compiler_common::EXTENSION_ERAVM_ASSEMBLY
|
||||
);
|
||||
let mut file_path = path.to_owned();
|
||||
file_path.push(file_name);
|
||||
|
||||
if file_path.exists() && !overwrite {
|
||||
eprintln!(
|
||||
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
|
||||
);
|
||||
} else {
|
||||
File::create(&file_path)
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!("File {:?} creating error: {}", file_path, error)
|
||||
})?
|
||||
.write_all(self.build.assembly_text.as_bytes())
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!("File {:?} writing error: {}", file_path, error)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
if output_binary {
|
||||
let file_name = format!(
|
||||
"{}.{}",
|
||||
file_name,
|
||||
era_compiler_common::EXTENSION_ERAVM_BINARY
|
||||
);
|
||||
let mut file_path = path.to_owned();
|
||||
file_path.push(file_name);
|
||||
|
||||
if file_path.exists() && !overwrite {
|
||||
eprintln!(
|
||||
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
|
||||
);
|
||||
} else {
|
||||
File::create(&file_path)
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!("File {:?} creating error: {}", file_path, error)
|
||||
})?
|
||||
.write_all(self.build.bytecode.as_slice())
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!("File {:?} writing error: {}", file_path, error)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Writes the contract text assembly and bytecode to the combined JSON.
|
||||
///
|
||||
pub fn write_to_combined_json(
|
||||
self,
|
||||
combined_json_contract: &mut CombinedJsonContract,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(metadata) = combined_json_contract.metadata.as_mut() {
|
||||
*metadata = self.metadata_json.to_string();
|
||||
}
|
||||
|
||||
if let Some(asm) = combined_json_contract.asm.as_mut() {
|
||||
*asm = serde_json::Value::String(self.build.assembly_text);
|
||||
}
|
||||
let hexadecimal_bytecode = hex::encode(self.build.bytecode);
|
||||
combined_json_contract.bin = Some(hexadecimal_bytecode);
|
||||
combined_json_contract.bin_runtime = combined_json_contract.bin.clone();
|
||||
|
||||
combined_json_contract.factory_deps = Some(self.build.factory_dependencies);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Writes the contract text assembly and bytecode to the standard JSON.
|
||||
///
|
||||
pub fn write_to_standard_json(
|
||||
self,
|
||||
standard_json_contract: &mut StandardJsonOutputContract,
|
||||
) -> anyhow::Result<()> {
|
||||
standard_json_contract.metadata = Some(self.metadata_json);
|
||||
|
||||
let assembly_text = self.build.assembly_text;
|
||||
let bytecode = hex::encode(self.build.bytecode.as_slice());
|
||||
if let Some(evm) = standard_json_contract.evm.as_mut() {
|
||||
evm.modify(assembly_text, bytecode);
|
||||
}
|
||||
|
||||
standard_json_contract.factory_dependencies = Some(self.build.factory_dependencies);
|
||||
standard_json_contract.hash = Some(self.build.bytecode_hash);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Converts the full path to a short one.
|
||||
///
|
||||
pub fn short_path(path: &str) -> &str {
|
||||
path.rfind('/')
|
||||
.map(|last_slash| &path[last_slash + 1..])
|
||||
.unwrap_or_else(|| path)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//!
|
||||
//! The Solidity project build.
|
||||
//!
|
||||
|
||||
pub mod contract;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::solc::combined_json::CombinedJson;
|
||||
use crate::solc::standard_json::output::Output as StandardJsonOutput;
|
||||
use crate::solc::version::Version as SolcVersion;
|
||||
|
||||
use self::contract::Contract;
|
||||
|
||||
///
|
||||
/// The Solidity project build.
|
||||
///
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Build {
|
||||
/// The contract data,
|
||||
pub contracts: BTreeMap<String, Contract>,
|
||||
}
|
||||
|
||||
impl Build {
|
||||
///
|
||||
/// Writes all contracts to the specified directory.
|
||||
///
|
||||
pub fn write_to_directory(
|
||||
self,
|
||||
output_directory: &Path,
|
||||
output_assembly: bool,
|
||||
output_binary: bool,
|
||||
overwrite: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
for (_path, contract) in self.contracts.into_iter() {
|
||||
contract.write_to_directory(
|
||||
output_directory,
|
||||
output_assembly,
|
||||
output_binary,
|
||||
overwrite,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Writes all contracts assembly and bytecode to the combined JSON.
|
||||
///
|
||||
pub fn write_to_combined_json(
|
||||
self,
|
||||
combined_json: &mut CombinedJson,
|
||||
zksolc_version: &semver::Version,
|
||||
) -> anyhow::Result<()> {
|
||||
for (path, contract) in self.contracts.into_iter() {
|
||||
let combined_json_contract = combined_json
|
||||
.contracts
|
||||
.iter_mut()
|
||||
.find_map(|(json_path, contract)| {
|
||||
if path.ends_with(json_path) {
|
||||
Some(contract)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| anyhow::anyhow!("Contract `{}` not found in the project", path))?;
|
||||
|
||||
contract.write_to_combined_json(combined_json_contract)?;
|
||||
}
|
||||
|
||||
combined_json.zk_version = Some(zksolc_version.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Writes all contracts assembly and bytecode to the standard JSON.
|
||||
///
|
||||
pub fn write_to_standard_json(
|
||||
mut self,
|
||||
standard_json: &mut StandardJsonOutput,
|
||||
solc_version: &SolcVersion,
|
||||
zksolc_version: &semver::Version,
|
||||
) -> anyhow::Result<()> {
|
||||
let contracts = match standard_json.contracts.as_mut() {
|
||||
Some(contracts) => contracts,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
for (path, contracts) in contracts.iter_mut() {
|
||||
for (name, contract) in contracts.iter_mut() {
|
||||
let full_name = format!("{path}:{name}");
|
||||
|
||||
if let Some(contract_data) = self.contracts.remove(full_name.as_str()) {
|
||||
contract_data.write_to_standard_json(contract)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
standard_json.version = Some(solc_version.default.to_string());
|
||||
standard_json.long_version = Some(solc_version.long.to_owned());
|
||||
standard_json.zk_version = Some(zksolc_version.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//!
|
||||
//! Solidity to EraVM compiler constants.
|
||||
//!
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
/// The default executable name.
|
||||
pub static DEFAULT_EXECUTABLE_NAME: &str = "zksolc";
|
||||
|
||||
/// The `keccak256` scratch space offset.
|
||||
pub const OFFSET_SCRATCH_SPACE: usize = 0;
|
||||
|
||||
/// The memory pointer offset.
|
||||
pub const OFFSET_MEMORY_POINTER: usize = 2 * era_compiler_common::BYTE_LENGTH_FIELD;
|
||||
|
||||
/// The empty slot offset.
|
||||
pub const OFFSET_EMPTY_SLOT: usize = 3 * era_compiler_common::BYTE_LENGTH_FIELD;
|
||||
|
||||
/// The non-reserved memory offset.
|
||||
pub const OFFSET_NON_RESERVED: usize = 4 * era_compiler_common::BYTE_LENGTH_FIELD;
|
||||
@@ -0,0 +1,79 @@
|
||||
//!
|
||||
//! The inner JSON legacy assembly code element.
|
||||
//!
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::evmla::assembly::Assembly;
|
||||
|
||||
///
|
||||
/// The inner JSON legacy assembly code element.
|
||||
///
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum Data {
|
||||
/// The assembly code wrapper.
|
||||
Assembly(Assembly),
|
||||
/// The hash.
|
||||
Hash(String),
|
||||
/// The full contract path after the factory dependencies replacing pass.
|
||||
Path(String),
|
||||
}
|
||||
|
||||
impl Data {
|
||||
///
|
||||
/// Returns the inner assembly reference if it is present.
|
||||
///
|
||||
pub fn get_assembly(&self) -> Option<&Assembly> {
|
||||
match self {
|
||||
Self::Assembly(ref assembly) => Some(assembly),
|
||||
Self::Hash(_) => None,
|
||||
Self::Path(_) => None,
|
||||
}
|
||||
}
|
||||
///
|
||||
/// Returns the inner assembly mutable reference if it is present.
|
||||
///
|
||||
pub fn get_assembly_mut(&mut self) -> Option<&mut Assembly> {
|
||||
match self {
|
||||
Self::Assembly(ref mut assembly) => Some(assembly),
|
||||
Self::Hash(_) => None,
|
||||
Self::Path(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
match self {
|
||||
Self::Assembly(assembly) => assembly.get_missing_libraries(),
|
||||
Self::Hash(_) => HashSet::new(),
|
||||
Self::Path(_) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Gets the contract `keccak256` hash.
|
||||
///
|
||||
pub fn keccak256(&self) -> String {
|
||||
match self {
|
||||
Self::Assembly(assembly) => assembly.keccak256(),
|
||||
Self::Hash(hash) => panic!("Expected assembly, found hash `{hash}`"),
|
||||
Self::Path(path) => panic!("Expected assembly, found path `{path}`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Data {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Assembly(inner) => writeln!(f, "{inner}"),
|
||||
Self::Hash(inner) => writeln!(f, "Hash `{inner}`"),
|
||||
Self::Path(inner) => writeln!(f, "Path `{inner}`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//!
|
||||
//! Translates the CODECOPY use cases.
|
||||
//!
|
||||
|
||||
///
|
||||
/// Translates the contract hash copying.
|
||||
///
|
||||
pub fn contract_hash<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
offset: inkwell::values::IntValue<'ctx>,
|
||||
value: inkwell::values::IntValue<'ctx>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let offset = context.builder().build_int_add(
|
||||
offset,
|
||||
context.field_const(
|
||||
(era_compiler_common::BYTE_LENGTH_X32 + era_compiler_common::BYTE_LENGTH_FIELD) as u64,
|
||||
),
|
||||
"datacopy_contract_hash_offset",
|
||||
)?;
|
||||
|
||||
era_compiler_llvm_context::eravm_evm_memory::store(context, offset, value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the library marker copying.
|
||||
///
|
||||
pub fn library_marker<D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
offset: u64,
|
||||
value: u64,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
era_compiler_llvm_context::eravm_evm_memory::store_byte(
|
||||
context,
|
||||
context.field_const(offset),
|
||||
context.field_const(value),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the static data copying.
|
||||
///
|
||||
pub fn static_data<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
destination: inkwell::values::IntValue<'ctx>,
|
||||
source: &str,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let mut offset = 0;
|
||||
for (index, chunk) in source
|
||||
.chars()
|
||||
.collect::<Vec<char>>()
|
||||
.chunks(era_compiler_common::BYTE_LENGTH_FIELD * 2)
|
||||
.enumerate()
|
||||
{
|
||||
let mut value_string = chunk.iter().collect::<String>();
|
||||
value_string.push_str(
|
||||
"0".repeat((era_compiler_common::BYTE_LENGTH_FIELD * 2) - chunk.len())
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let datacopy_destination = context.builder().build_int_add(
|
||||
destination,
|
||||
context.field_const(offset as u64),
|
||||
format!("datacopy_destination_index_{index}").as_str(),
|
||||
)?;
|
||||
let datacopy_value = context.field_const_str_hex(value_string.as_str());
|
||||
era_compiler_llvm_context::eravm_evm_memory::store(
|
||||
context,
|
||||
datacopy_destination,
|
||||
datacopy_value,
|
||||
)?;
|
||||
offset += chunk.len() / 2;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//!
|
||||
//! Translates the jump operations.
|
||||
//!
|
||||
|
||||
///
|
||||
/// Translates the unconditional jump.
|
||||
///
|
||||
pub fn unconditional<D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
destination: num::BigUint,
|
||||
stack_hash: md5::Digest,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let code_type = context
|
||||
.code_type()
|
||||
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
|
||||
let block_key = era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, destination);
|
||||
|
||||
let block = context
|
||||
.current_function()
|
||||
.borrow()
|
||||
.evmla()
|
||||
.find_block(&block_key, &stack_hash)?;
|
||||
context.build_unconditional_branch(block.inner());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the conditional jump.
|
||||
///
|
||||
pub fn conditional<D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
destination: num::BigUint,
|
||||
stack_hash: md5::Digest,
|
||||
stack_height: usize,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let code_type = context
|
||||
.code_type()
|
||||
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
|
||||
let block_key = era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, destination);
|
||||
|
||||
let condition_pointer = context.evmla().stack[stack_height]
|
||||
.to_llvm()
|
||||
.into_pointer_value();
|
||||
let condition = context.build_load(
|
||||
era_compiler_llvm_context::EraVMPointer::new_stack_field(context, condition_pointer),
|
||||
format!("conditional_{block_key}_condition").as_str(),
|
||||
)?;
|
||||
let condition = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::NE,
|
||||
condition.into_int_value(),
|
||||
context.field_const(0),
|
||||
format!("conditional_{block_key}_condition_compared").as_str(),
|
||||
)?;
|
||||
|
||||
let then_block = context
|
||||
.current_function()
|
||||
.borrow()
|
||||
.evmla()
|
||||
.find_block(&block_key, &stack_hash)?;
|
||||
let join_block =
|
||||
context.append_basic_block(format!("conditional_{block_key}_join_block").as_str());
|
||||
|
||||
context.build_conditional_branch(condition, then_block.inner(), join_block)?;
|
||||
|
||||
context.set_basic_block(join_block);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
//!
|
||||
//! The EVM instruction.
|
||||
//!
|
||||
|
||||
pub mod codecopy;
|
||||
pub mod jump;
|
||||
pub mod name;
|
||||
pub mod stack;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::name::Name;
|
||||
|
||||
///
|
||||
/// The EVM instruction.
|
||||
///
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Instruction {
|
||||
/// The opcode or tag identifier.
|
||||
pub name: Name,
|
||||
/// The optional value argument.
|
||||
pub value: Option<String>,
|
||||
|
||||
/// The source code identifier.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<isize>,
|
||||
/// The source code location begin.
|
||||
pub begin: isize,
|
||||
/// The source code location end.
|
||||
pub end: isize,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
///
|
||||
/// Returns the number of input stack arguments.
|
||||
///
|
||||
pub const fn input_size(&self, version: &semver::Version) -> usize {
|
||||
match self.name {
|
||||
Name::POP => 1,
|
||||
|
||||
Name::JUMP => 1,
|
||||
Name::JUMPI => 2,
|
||||
|
||||
Name::ADD => 2,
|
||||
Name::SUB => 2,
|
||||
Name::MUL => 2,
|
||||
Name::DIV => 2,
|
||||
Name::MOD => 2,
|
||||
Name::SDIV => 2,
|
||||
Name::SMOD => 2,
|
||||
|
||||
Name::LT => 2,
|
||||
Name::GT => 2,
|
||||
Name::EQ => 2,
|
||||
Name::ISZERO => 1,
|
||||
Name::SLT => 2,
|
||||
Name::SGT => 2,
|
||||
|
||||
Name::OR => 2,
|
||||
Name::XOR => 2,
|
||||
Name::NOT => 1,
|
||||
Name::AND => 2,
|
||||
Name::SHL => 2,
|
||||
Name::SHR => 2,
|
||||
Name::SAR => 2,
|
||||
Name::BYTE => 2,
|
||||
|
||||
Name::ADDMOD => 3,
|
||||
Name::MULMOD => 3,
|
||||
Name::EXP => 2,
|
||||
Name::SIGNEXTEND => 2,
|
||||
Name::SHA3 => 2,
|
||||
Name::KECCAK256 => 2,
|
||||
|
||||
Name::MLOAD => 1,
|
||||
Name::MSTORE => 2,
|
||||
Name::MSTORE8 => 2,
|
||||
Name::MCOPY => 3,
|
||||
|
||||
Name::SLOAD => 1,
|
||||
Name::SSTORE => 2,
|
||||
Name::TLOAD => 1,
|
||||
Name::TSTORE => 2,
|
||||
Name::PUSHIMMUTABLE => 0,
|
||||
Name::ASSIGNIMMUTABLE => {
|
||||
if version.minor >= 8 {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
Name::CALLDATALOAD => 1,
|
||||
Name::CALLDATACOPY => 3,
|
||||
Name::CODECOPY => 3,
|
||||
Name::RETURNDATACOPY => 3,
|
||||
Name::EXTCODESIZE => 1,
|
||||
Name::EXTCODEHASH => 1,
|
||||
|
||||
Name::CALL => 7,
|
||||
Name::CALLCODE => 7,
|
||||
Name::STATICCALL => 6,
|
||||
Name::DELEGATECALL => 6,
|
||||
|
||||
Name::RETURN => 2,
|
||||
Name::REVERT => 2,
|
||||
Name::SELFDESTRUCT => 1,
|
||||
|
||||
Name::LOG0 => 2,
|
||||
Name::LOG1 => 3,
|
||||
Name::LOG2 => 4,
|
||||
Name::LOG3 => 5,
|
||||
Name::LOG4 => 6,
|
||||
|
||||
Name::CREATE => 3,
|
||||
Name::CREATE2 => 4,
|
||||
|
||||
Name::ZK_CREATE => 3,
|
||||
Name::ZK_CREATE2 => 4,
|
||||
|
||||
Name::BALANCE => 1,
|
||||
|
||||
Name::BLOCKHASH => 1,
|
||||
Name::BLOBHASH => 1,
|
||||
|
||||
Name::EXTCODECOPY => 4,
|
||||
|
||||
Name::RecursiveCall { input_size, .. } => input_size,
|
||||
Name::RecursiveReturn { input_size } => input_size,
|
||||
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the number of output stack arguments.
|
||||
///
|
||||
pub const fn output_size(&self) -> usize {
|
||||
match self.name {
|
||||
Name::PUSH => 1,
|
||||
Name::PUSH_Data => 1,
|
||||
Name::PUSH_Tag => 1,
|
||||
Name::PUSH_ContractHash => 1,
|
||||
Name::PUSH_ContractHashSize => 1,
|
||||
Name::PUSHLIB => 1,
|
||||
Name::PUSHDEPLOYADDRESS => 1,
|
||||
|
||||
Name::PUSH1 => 1,
|
||||
Name::PUSH2 => 1,
|
||||
Name::PUSH3 => 1,
|
||||
Name::PUSH4 => 1,
|
||||
Name::PUSH5 => 1,
|
||||
Name::PUSH6 => 1,
|
||||
Name::PUSH7 => 1,
|
||||
Name::PUSH8 => 1,
|
||||
Name::PUSH9 => 1,
|
||||
Name::PUSH10 => 1,
|
||||
Name::PUSH11 => 1,
|
||||
Name::PUSH12 => 1,
|
||||
Name::PUSH13 => 1,
|
||||
Name::PUSH14 => 1,
|
||||
Name::PUSH15 => 1,
|
||||
Name::PUSH16 => 1,
|
||||
Name::PUSH17 => 1,
|
||||
Name::PUSH18 => 1,
|
||||
Name::PUSH19 => 1,
|
||||
Name::PUSH20 => 1,
|
||||
Name::PUSH21 => 1,
|
||||
Name::PUSH22 => 1,
|
||||
Name::PUSH23 => 1,
|
||||
Name::PUSH24 => 1,
|
||||
Name::PUSH25 => 1,
|
||||
Name::PUSH26 => 1,
|
||||
Name::PUSH27 => 1,
|
||||
Name::PUSH28 => 1,
|
||||
Name::PUSH29 => 1,
|
||||
Name::PUSH30 => 1,
|
||||
Name::PUSH31 => 1,
|
||||
Name::PUSH32 => 1,
|
||||
|
||||
Name::DUP1 => 1,
|
||||
Name::DUP2 => 1,
|
||||
Name::DUP3 => 1,
|
||||
Name::DUP4 => 1,
|
||||
Name::DUP5 => 1,
|
||||
Name::DUP6 => 1,
|
||||
Name::DUP7 => 1,
|
||||
Name::DUP8 => 1,
|
||||
Name::DUP9 => 1,
|
||||
Name::DUP10 => 1,
|
||||
Name::DUP11 => 1,
|
||||
Name::DUP12 => 1,
|
||||
Name::DUP13 => 1,
|
||||
Name::DUP14 => 1,
|
||||
Name::DUP15 => 1,
|
||||
Name::DUP16 => 1,
|
||||
|
||||
Name::ADD => 1,
|
||||
Name::SUB => 1,
|
||||
Name::MUL => 1,
|
||||
Name::DIV => 1,
|
||||
Name::MOD => 1,
|
||||
Name::SDIV => 1,
|
||||
Name::SMOD => 1,
|
||||
|
||||
Name::LT => 1,
|
||||
Name::GT => 1,
|
||||
Name::EQ => 1,
|
||||
Name::ISZERO => 1,
|
||||
Name::SLT => 1,
|
||||
Name::SGT => 1,
|
||||
|
||||
Name::OR => 1,
|
||||
Name::XOR => 1,
|
||||
Name::NOT => 1,
|
||||
Name::AND => 1,
|
||||
Name::SHL => 1,
|
||||
Name::SHR => 1,
|
||||
Name::SAR => 1,
|
||||
Name::BYTE => 1,
|
||||
|
||||
Name::ADDMOD => 1,
|
||||
Name::MULMOD => 1,
|
||||
Name::EXP => 1,
|
||||
Name::SIGNEXTEND => 1,
|
||||
Name::SHA3 => 1,
|
||||
Name::KECCAK256 => 1,
|
||||
|
||||
Name::MLOAD => 1,
|
||||
|
||||
Name::SLOAD => 1,
|
||||
Name::TLOAD => 1,
|
||||
Name::PUSHIMMUTABLE => 1,
|
||||
|
||||
Name::CALLDATALOAD => 1,
|
||||
Name::CALLDATASIZE => 1,
|
||||
Name::CODESIZE => 1,
|
||||
Name::PUSHSIZE => 1,
|
||||
Name::RETURNDATASIZE => 1,
|
||||
Name::EXTCODESIZE => 1,
|
||||
Name::EXTCODEHASH => 1,
|
||||
|
||||
Name::CALL => 1,
|
||||
Name::CALLCODE => 1,
|
||||
Name::STATICCALL => 1,
|
||||
Name::DELEGATECALL => 1,
|
||||
|
||||
Name::CREATE => 1,
|
||||
Name::CREATE2 => 1,
|
||||
|
||||
Name::ZK_CREATE => 1,
|
||||
Name::ZK_CREATE2 => 1,
|
||||
|
||||
Name::ADDRESS => 1,
|
||||
Name::CALLER => 1,
|
||||
Name::TIMESTAMP => 1,
|
||||
Name::NUMBER => 1,
|
||||
|
||||
Name::CALLVALUE => 1,
|
||||
Name::GAS => 1,
|
||||
Name::BALANCE => 1,
|
||||
Name::SELFBALANCE => 1,
|
||||
|
||||
Name::GASLIMIT => 1,
|
||||
Name::GASPRICE => 1,
|
||||
Name::ORIGIN => 1,
|
||||
Name::CHAINID => 1,
|
||||
Name::BLOCKHASH => 1,
|
||||
Name::BLOBHASH => 1,
|
||||
Name::DIFFICULTY => 1,
|
||||
Name::PREVRANDAO => 1,
|
||||
Name::COINBASE => 1,
|
||||
Name::MSIZE => 1,
|
||||
|
||||
Name::BASEFEE => 1,
|
||||
Name::BLOBBASEFEE => 1,
|
||||
Name::PC => 1,
|
||||
|
||||
Name::RecursiveCall { output_size, .. } => output_size,
|
||||
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Replaces the instruction data aliases with the actual data.
|
||||
///
|
||||
pub fn replace_data_aliases(
|
||||
instructions: &mut [Self],
|
||||
mapping: &BTreeMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
for instruction in instructions.iter_mut() {
|
||||
match instruction {
|
||||
Instruction {
|
||||
name: Name::PUSH_ContractHash | Name::PUSH_ContractHashSize,
|
||||
value: Some(value),
|
||||
..
|
||||
} => {
|
||||
*value = mapping.get(value.as_str()).cloned().ok_or_else(|| {
|
||||
anyhow::anyhow!("Contract alias `{}` data not found", value)
|
||||
})?;
|
||||
}
|
||||
Instruction {
|
||||
name: Name::PUSH_Data,
|
||||
value: Some(value),
|
||||
..
|
||||
} => {
|
||||
let mut key_extended =
|
||||
"0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - value.len());
|
||||
key_extended.push_str(value.as_str());
|
||||
|
||||
*value = mapping.get(key_extended.as_str()).cloned().ok_or_else(|| {
|
||||
anyhow::anyhow!("Data chunk alias `{}` data not found", key_extended)
|
||||
})?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Initializes an `INVALID` instruction to terminate an invalid unreachable block part.
|
||||
///
|
||||
pub fn invalid(previous: &Self) -> Self {
|
||||
Self {
|
||||
name: Name::INVALID,
|
||||
value: None,
|
||||
|
||||
source: previous.source,
|
||||
begin: previous.begin,
|
||||
end: previous.end,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Initializes a recursive function `Call` instruction.
|
||||
///
|
||||
pub fn recursive_call(
|
||||
name: String,
|
||||
entry_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
stack_hash: md5::Digest,
|
||||
input_size: usize,
|
||||
output_size: usize,
|
||||
return_address: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
previous: &Self,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: Name::RecursiveCall {
|
||||
name,
|
||||
entry_key,
|
||||
stack_hash,
|
||||
input_size,
|
||||
output_size,
|
||||
return_address,
|
||||
},
|
||||
value: None,
|
||||
|
||||
source: previous.source,
|
||||
begin: previous.begin,
|
||||
end: previous.end,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Initializes a recursive function `Return` instruction.
|
||||
///
|
||||
pub fn recursive_return(input_size: usize, previous: &Self) -> Self {
|
||||
Self {
|
||||
name: Name::RecursiveReturn { input_size },
|
||||
value: None,
|
||||
|
||||
source: previous.source,
|
||||
begin: previous.begin,
|
||||
end: previous.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Instruction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let name = self.name.to_string();
|
||||
match self.name {
|
||||
Name::Tag => write!(f, "{:4}", name),
|
||||
_ => write!(f, "{:15}", name),
|
||||
}?;
|
||||
match self.value {
|
||||
Some(ref value) if value.len() <= 64 => write!(f, "{}", value)?,
|
||||
Some(ref value) => write!(f, "... {}", &value[value.len() - 60..])?,
|
||||
None => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
//!
|
||||
//! The EVM instruction name.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The EVM instruction name.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum Name {
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH,
|
||||
/// Pushes a constant tag index.
|
||||
#[serde(rename = "PUSH [tag]")]
|
||||
PUSH_Tag,
|
||||
/// Pushes an unknown `data` value.
|
||||
#[serde(rename = "PUSH data")]
|
||||
PUSH_Data,
|
||||
/// Pushes a contract hash size.
|
||||
#[serde(rename = "PUSH #[$]")]
|
||||
PUSH_ContractHashSize,
|
||||
/// Pushes a contract hash.
|
||||
#[serde(rename = "PUSH [$]")]
|
||||
PUSH_ContractHash,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH1,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH2,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH3,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH4,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH5,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH6,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH7,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH8,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH9,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH10,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH11,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH12,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH13,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH14,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH15,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH16,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH17,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH18,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH19,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH20,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH21,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH22,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH23,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH24,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH25,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH26,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH27,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH28,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH29,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH30,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH31,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH32,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
DUP1,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP2,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP3,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP4,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP5,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP6,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP7,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP8,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP9,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP10,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP11,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP12,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP13,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP14,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP15,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP16,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP1,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP2,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP3,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP4,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP5,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP6,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP7,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP8,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP9,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP10,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP11,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP12,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP13,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP14,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP15,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP16,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
POP,
|
||||
|
||||
/// Sets the current basic code block.
|
||||
#[serde(rename = "tag")]
|
||||
Tag,
|
||||
/// The eponymous EVM instruction.
|
||||
JUMP,
|
||||
/// The eponymous EVM instruction.
|
||||
JUMPI,
|
||||
/// The eponymous EVM instruction.
|
||||
JUMPDEST,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
ADD,
|
||||
/// The eponymous EVM instruction.
|
||||
SUB,
|
||||
/// The eponymous EVM instruction.
|
||||
MUL,
|
||||
/// The eponymous EVM instruction.
|
||||
DIV,
|
||||
/// The eponymous EVM instruction.
|
||||
MOD,
|
||||
/// The eponymous EVM instruction.
|
||||
SDIV,
|
||||
/// The eponymous EVM instruction.
|
||||
SMOD,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
LT,
|
||||
/// The eponymous EVM instruction.
|
||||
GT,
|
||||
/// The eponymous EVM instruction.
|
||||
EQ,
|
||||
/// The eponymous EVM instruction.
|
||||
ISZERO,
|
||||
/// The eponymous EVM instruction.
|
||||
SLT,
|
||||
/// The eponymous EVM instruction.
|
||||
SGT,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
OR,
|
||||
/// The eponymous EVM instruction.
|
||||
XOR,
|
||||
/// The eponymous EVM instruction.
|
||||
NOT,
|
||||
/// The eponymous EVM instruction.
|
||||
AND,
|
||||
/// The eponymous EVM instruction.
|
||||
SHL,
|
||||
/// The eponymous EVM instruction.
|
||||
SHR,
|
||||
/// The eponymous EVM instruction.
|
||||
SAR,
|
||||
/// The eponymous EVM instruction.
|
||||
BYTE,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
ADDMOD,
|
||||
/// The eponymous EVM instruction.
|
||||
MULMOD,
|
||||
/// The eponymous EVM instruction.
|
||||
EXP,
|
||||
/// The eponymous EVM instruction.
|
||||
SIGNEXTEND,
|
||||
/// The eponymous EVM instruction.
|
||||
SHA3,
|
||||
/// The eponymous EVM instruction.
|
||||
KECCAK256,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
MLOAD,
|
||||
/// The eponymous EVM instruction.
|
||||
MSTORE,
|
||||
/// The eponymous EVM instruction.
|
||||
MSTORE8,
|
||||
/// The eponymous EVM instruction.
|
||||
MCOPY,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
SLOAD,
|
||||
/// The eponymous EVM instruction.
|
||||
SSTORE,
|
||||
/// The eponymous EVM instruction.
|
||||
TLOAD,
|
||||
/// The eponymous EVM instruction.
|
||||
TSTORE,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSHIMMUTABLE,
|
||||
/// The eponymous EVM instruction.
|
||||
ASSIGNIMMUTABLE,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CALLDATALOAD,
|
||||
/// The eponymous EVM instruction.
|
||||
CALLDATASIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
CALLDATACOPY,
|
||||
/// The eponymous EVM instruction.
|
||||
CODESIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
CODECOPY,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSHSIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
EXTCODESIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
EXTCODEHASH,
|
||||
/// The eponymous EVM instruction.
|
||||
RETURNDATASIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
RETURNDATACOPY,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
RETURN,
|
||||
/// The eponymous EVM instruction.
|
||||
REVERT,
|
||||
/// The eponymous EVM instruction.
|
||||
STOP,
|
||||
/// The eponymous EVM instruction.
|
||||
INVALID,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
LOG0,
|
||||
/// The eponymous EVM instruction.
|
||||
LOG1,
|
||||
/// The eponymous EVM instruction.
|
||||
LOG2,
|
||||
/// The eponymous EVM instruction.
|
||||
LOG3,
|
||||
/// The eponymous EVM instruction.
|
||||
LOG4,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CALL,
|
||||
/// The eponymous EVM instruction.
|
||||
STATICCALL,
|
||||
/// The eponymous EVM instruction.
|
||||
DELEGATECALL,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CREATE,
|
||||
/// The eponymous EVM instruction.
|
||||
CREATE2,
|
||||
|
||||
/// The eponymous EraVM instruction.
|
||||
#[serde(rename = "$ZK_CREATE")]
|
||||
ZK_CREATE,
|
||||
/// The eponymous EraVM instruction.
|
||||
#[serde(rename = "$ZK_CREATE2")]
|
||||
ZK_CREATE2,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
ADDRESS,
|
||||
/// The eponymous EVM instruction.
|
||||
CALLER,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CALLVALUE,
|
||||
/// The eponymous EVM instruction.
|
||||
GAS,
|
||||
/// The eponymous EVM instruction.
|
||||
BALANCE,
|
||||
/// The eponymous EVM instruction.
|
||||
SELFBALANCE,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
PUSHLIB,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSHDEPLOYADDRESS,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
GASLIMIT,
|
||||
/// The eponymous EVM instruction.
|
||||
GASPRICE,
|
||||
/// The eponymous EVM instruction.
|
||||
ORIGIN,
|
||||
/// The eponymous EVM instruction.
|
||||
CHAINID,
|
||||
/// The eponymous EVM instruction.
|
||||
TIMESTAMP,
|
||||
/// The eponymous EVM instruction.
|
||||
NUMBER,
|
||||
/// The eponymous EVM instruction.
|
||||
BLOCKHASH,
|
||||
/// The eponymous EVM instruction.
|
||||
BLOBHASH,
|
||||
/// The eponymous EVM instruction.
|
||||
DIFFICULTY,
|
||||
/// The eponymous EVM instruction.
|
||||
PREVRANDAO,
|
||||
/// The eponymous EVM instruction.
|
||||
COINBASE,
|
||||
/// The eponymous EVM instruction.
|
||||
BASEFEE,
|
||||
/// The eponymous EVM instruction.
|
||||
BLOBBASEFEE,
|
||||
/// The eponymous EVM instruction.
|
||||
MSIZE,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CALLCODE,
|
||||
/// The eponymous EVM instruction.
|
||||
PC,
|
||||
/// The eponymous EVM instruction.
|
||||
EXTCODECOPY,
|
||||
/// The eponymous EVM instruction.
|
||||
SELFDESTRUCT,
|
||||
|
||||
/// The recursive function call instruction.
|
||||
#[serde(skip)]
|
||||
RecursiveCall {
|
||||
/// The called function name.
|
||||
name: String,
|
||||
/// The called function key.
|
||||
entry_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The stack state hash after return.
|
||||
stack_hash: md5::Digest,
|
||||
/// The input size.
|
||||
input_size: usize,
|
||||
/// The output size.
|
||||
output_size: usize,
|
||||
/// The return address.
|
||||
return_address: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
},
|
||||
/// The recursive function return instruction.
|
||||
#[serde(skip)]
|
||||
RecursiveReturn {
|
||||
/// The output size.
|
||||
input_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Tag => write!(f, "Tag"),
|
||||
Self::RecursiveCall {
|
||||
name,
|
||||
entry_key,
|
||||
input_size,
|
||||
output_size,
|
||||
return_address,
|
||||
..
|
||||
} => write!(
|
||||
f,
|
||||
"RECURSIVE_CALL({}_{}, {}, {}, {})",
|
||||
name, entry_key, input_size, output_size, return_address
|
||||
),
|
||||
Self::RecursiveReturn { input_size } => write!(f, "RECURSIVE_RETURN({})", input_size),
|
||||
_ => write!(
|
||||
f,
|
||||
"{}",
|
||||
serde_json::to_string(self)
|
||||
.expect("Always valid")
|
||||
.trim_matches('\"')
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//!
|
||||
//! Translates the stack memory operations.
|
||||
//!
|
||||
|
||||
use inkwell::values::BasicValue;
|
||||
|
||||
///
|
||||
/// Translates the ordinar value push.
|
||||
///
|
||||
pub fn push<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
value: String,
|
||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let result = context
|
||||
.field_type()
|
||||
.const_int_from_string(
|
||||
value.to_ascii_uppercase().as_str(),
|
||||
inkwell::types::StringRadix::Hexadecimal,
|
||||
)
|
||||
.expect("Always valid")
|
||||
.as_basic_value_enum();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the block tag label push.
|
||||
///
|
||||
pub fn push_tag<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
value: String,
|
||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let result = context
|
||||
.field_type()
|
||||
.const_int_from_string(value.as_str(), inkwell::types::StringRadix::Decimal)
|
||||
.expect("Always valid");
|
||||
Ok(result.as_basic_value_enum())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the stack memory duplicate.
|
||||
///
|
||||
pub fn dup<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
offset: usize,
|
||||
height: usize,
|
||||
original: &mut Option<String>,
|
||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let element = &context.evmla().stack[height - offset - 1];
|
||||
let value = context.build_load(
|
||||
era_compiler_llvm_context::EraVMPointer::new_stack_field(
|
||||
context,
|
||||
element.to_llvm().into_pointer_value(),
|
||||
),
|
||||
format!("dup{offset}").as_str(),
|
||||
)?;
|
||||
|
||||
*original = element.original.to_owned();
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the stack memory swap.
|
||||
///
|
||||
pub fn swap<D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
offset: usize,
|
||||
height: usize,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let top_element = context.evmla().stack[height - 1].to_owned();
|
||||
let top_pointer = era_compiler_llvm_context::EraVMPointer::new_stack_field(
|
||||
context,
|
||||
top_element.to_llvm().into_pointer_value(),
|
||||
);
|
||||
let top_value = context.build_load(top_pointer, format!("swap{offset}_top_value").as_str())?;
|
||||
|
||||
let swap_element = context.evmla().stack[height - offset - 1].to_owned();
|
||||
let swap_pointer = era_compiler_llvm_context::EraVMPointer::new_stack_field(
|
||||
context,
|
||||
swap_element.to_llvm().into_pointer_value(),
|
||||
);
|
||||
let swap_value =
|
||||
context.build_load(swap_pointer, format!("swap{offset}_swap_value").as_str())?;
|
||||
|
||||
context.evmla_mut().stack[height - 1].original = swap_element.original.to_owned();
|
||||
context.evmla_mut().stack[height - offset - 1].original = top_element.original.to_owned();
|
||||
|
||||
context.build_store(top_pointer, swap_value)?;
|
||||
context.build_store(swap_pointer, top_value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the stack memory pop.
|
||||
///
|
||||
pub fn pop<D>(_context: &mut era_compiler_llvm_context::EraVMContext<D>) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
//!
|
||||
//! The `solc --asm-json` output.
|
||||
//!
|
||||
|
||||
pub mod data;
|
||||
pub mod instruction;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::evmla::ethereal_ir::entry_link::EntryLink;
|
||||
use crate::evmla::ethereal_ir::EtherealIR;
|
||||
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
|
||||
|
||||
use self::data::Data;
|
||||
use self::instruction::name::Name as InstructionName;
|
||||
use self::instruction::Instruction;
|
||||
|
||||
///
|
||||
/// The JSON assembly.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Assembly {
|
||||
/// The metadata string.
|
||||
#[serde(rename = ".auxdata")]
|
||||
pub auxdata: Option<String>,
|
||||
/// The deploy code instructions.
|
||||
#[serde(rename = ".code")]
|
||||
pub code: Option<Vec<Instruction>>,
|
||||
/// The runtime code.
|
||||
#[serde(rename = ".data")]
|
||||
pub data: Option<BTreeMap<String, Data>>,
|
||||
|
||||
/// The full contract path.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub full_path: Option<String>,
|
||||
/// The factory dependency paths.
|
||||
#[serde(default = "HashSet::new")]
|
||||
pub factory_dependencies: HashSet<String>,
|
||||
/// The EVMLA extra metadata.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extra_metadata: Option<ExtraMetadata>,
|
||||
}
|
||||
|
||||
impl Assembly {
|
||||
///
|
||||
/// Gets the contract `keccak256` hash.
|
||||
///
|
||||
pub fn keccak256(&self) -> String {
|
||||
let json = serde_json::to_vec(self).expect("Always valid");
|
||||
era_compiler_llvm_context::eravm_utils::keccak256(json.as_slice())
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the full contract path.
|
||||
///
|
||||
pub fn set_full_path(&mut self, full_path: String) {
|
||||
self.full_path = Some(full_path);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the full contract path if it is set, or `<undefined>` otherwise.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the `full_path` has not been set.
|
||||
///
|
||||
pub fn full_path(&self) -> &str {
|
||||
self.full_path
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| panic!("The full path of some contracts is unset"))
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
let mut missing_libraries = HashSet::new();
|
||||
if let Some(code) = self.code.as_ref() {
|
||||
for instruction in code.iter() {
|
||||
if let InstructionName::PUSHLIB = instruction.name {
|
||||
let library_path = instruction.value.to_owned().expect("Always exists");
|
||||
missing_libraries.insert(library_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(data) = self.data.as_ref() {
|
||||
for (_, data) in data.iter() {
|
||||
missing_libraries.extend(data.get_missing_libraries());
|
||||
}
|
||||
}
|
||||
missing_libraries
|
||||
}
|
||||
|
||||
///
|
||||
/// Replaces the deploy code dependencies with full contract path and returns the list.
|
||||
///
|
||||
pub fn deploy_dependencies_pass(
|
||||
&mut self,
|
||||
full_path: &str,
|
||||
hash_data_mapping: &BTreeMap<String, String>,
|
||||
) -> anyhow::Result<BTreeMap<String, String>> {
|
||||
let mut index_path_mapping = BTreeMap::new();
|
||||
let index = "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2);
|
||||
index_path_mapping.insert(index, full_path.to_owned());
|
||||
|
||||
let dependencies = match self.data.as_mut() {
|
||||
Some(dependencies) => dependencies,
|
||||
None => return Ok(index_path_mapping),
|
||||
};
|
||||
for (index, data) in dependencies.iter_mut() {
|
||||
if index == "0" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut index_extended =
|
||||
"0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - index.len());
|
||||
index_extended.push_str(index.as_str());
|
||||
|
||||
*data = match data {
|
||||
Data::Assembly(assembly) => {
|
||||
let hash = assembly.keccak256();
|
||||
let full_path =
|
||||
hash_data_mapping
|
||||
.get(hash.as_str())
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Contract path not found for hash `{}`", hash)
|
||||
})?;
|
||||
self.factory_dependencies.insert(full_path.to_owned());
|
||||
|
||||
index_path_mapping.insert(index_extended, full_path.clone());
|
||||
Data::Path(full_path)
|
||||
}
|
||||
Data::Hash(hash) => {
|
||||
index_path_mapping.insert(index_extended, hash.to_owned());
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(index_path_mapping)
|
||||
}
|
||||
|
||||
///
|
||||
/// Replaces the runtime code dependencies with full contract path and returns the list.
|
||||
///
|
||||
pub fn runtime_dependencies_pass(
|
||||
&mut self,
|
||||
full_path: &str,
|
||||
hash_data_mapping: &BTreeMap<String, String>,
|
||||
) -> anyhow::Result<BTreeMap<String, String>> {
|
||||
let mut index_path_mapping = BTreeMap::new();
|
||||
let index = "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2);
|
||||
index_path_mapping.insert(index, full_path.to_owned());
|
||||
|
||||
let dependencies = match self
|
||||
.data
|
||||
.as_mut()
|
||||
.and_then(|data| data.get_mut("0"))
|
||||
.and_then(|data| data.get_assembly_mut())
|
||||
.and_then(|assembly| assembly.data.as_mut())
|
||||
{
|
||||
Some(dependencies) => dependencies,
|
||||
None => return Ok(index_path_mapping),
|
||||
};
|
||||
for (index, data) in dependencies.iter_mut() {
|
||||
let mut index_extended =
|
||||
"0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - index.len());
|
||||
index_extended.push_str(index.as_str());
|
||||
|
||||
*data = match data {
|
||||
Data::Assembly(assembly) => {
|
||||
let hash = assembly.keccak256();
|
||||
let full_path =
|
||||
hash_data_mapping
|
||||
.get(hash.as_str())
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Contract path not found for hash `{}`", hash)
|
||||
})?;
|
||||
self.factory_dependencies.insert(full_path.to_owned());
|
||||
|
||||
index_path_mapping.insert(index_extended, full_path.clone());
|
||||
Data::Path(full_path)
|
||||
}
|
||||
Data::Hash(hash) => {
|
||||
index_path_mapping.insert(index_extended, hash.to_owned());
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(index_path_mapping)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Assembly
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut entry = era_compiler_llvm_context::EraVMEntryFunction::default();
|
||||
entry.declare(context)?;
|
||||
|
||||
let mut runtime = era_compiler_llvm_context::EraVMRuntime::new(
|
||||
era_compiler_llvm_context::EraVMAddressSpace::Heap,
|
||||
);
|
||||
runtime.declare(context)?;
|
||||
|
||||
era_compiler_llvm_context::EraVMDeployCodeFunction::new(
|
||||
era_compiler_llvm_context::EraVMDummyLLVMWritable::default(),
|
||||
)
|
||||
.declare(context)?;
|
||||
era_compiler_llvm_context::EraVMRuntimeCodeFunction::new(
|
||||
era_compiler_llvm_context::EraVMDummyLLVMWritable::default(),
|
||||
)
|
||||
.declare(context)?;
|
||||
|
||||
entry.into_llvm(context)?;
|
||||
|
||||
runtime.into_llvm(context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_llvm(
|
||||
mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
let full_path = self.full_path().to_owned();
|
||||
|
||||
if let Some(debug_config) = context.debug_config() {
|
||||
debug_config.dump_evmla(full_path.as_str(), self.to_string().as_str())?;
|
||||
}
|
||||
let deploy_code_blocks = EtherealIR::get_blocks(
|
||||
context.evmla().version.to_owned(),
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
self.code
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Deploy code instructions not found"))?,
|
||||
)?;
|
||||
|
||||
let data = self
|
||||
.data
|
||||
.ok_or_else(|| anyhow::anyhow!("Runtime code data not found"))?
|
||||
.remove("0")
|
||||
.expect("Always exists");
|
||||
if let Some(debug_config) = context.debug_config() {
|
||||
debug_config.dump_evmla(full_path.as_str(), data.to_string().as_str())?;
|
||||
}
|
||||
let runtime_code_instructions = match data {
|
||||
Data::Assembly(assembly) => assembly
|
||||
.code
|
||||
.ok_or_else(|| anyhow::anyhow!("Runtime code instructions not found"))?,
|
||||
Data::Hash(hash) => {
|
||||
anyhow::bail!("Expected runtime code instructions, found hash `{}`", hash)
|
||||
}
|
||||
Data::Path(path) => {
|
||||
anyhow::bail!("Expected runtime code instructions, found path `{}`", path)
|
||||
}
|
||||
};
|
||||
let runtime_code_blocks = EtherealIR::get_blocks(
|
||||
context.evmla().version.to_owned(),
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
runtime_code_instructions.as_slice(),
|
||||
)?;
|
||||
|
||||
let extra_metadata = self.extra_metadata.take().unwrap_or_default();
|
||||
let mut blocks = deploy_code_blocks;
|
||||
blocks.extend(runtime_code_blocks);
|
||||
let mut ethereal_ir =
|
||||
EtherealIR::new(context.evmla().version.to_owned(), extra_metadata, blocks)?;
|
||||
if let Some(debug_config) = context.debug_config() {
|
||||
debug_config.dump_ethir(full_path.as_str(), ethereal_ir.to_string().as_str())?;
|
||||
}
|
||||
ethereal_ir.declare(context)?;
|
||||
ethereal_ir.into_llvm(context)?;
|
||||
|
||||
era_compiler_llvm_context::EraVMDeployCodeFunction::new(EntryLink::new(
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
))
|
||||
.into_llvm(context)?;
|
||||
era_compiler_llvm_context::EraVMRuntimeCodeFunction::new(EntryLink::new(
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
))
|
||||
.into_llvm(context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Assembly {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(instructions) = self.code.as_ref() {
|
||||
for (index, instruction) in instructions.iter().enumerate() {
|
||||
match instruction.name {
|
||||
InstructionName::Tag => writeln!(f, "{index:03} {instruction}")?,
|
||||
_ => writeln!(f, "{index:03} {instruction}")?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//!
|
||||
//! The Ethereal IR entry function link.
|
||||
//!
|
||||
|
||||
use inkwell::values::BasicValue;
|
||||
|
||||
use crate::evmla::ethereal_ir::EtherealIR;
|
||||
|
||||
///
|
||||
/// The Ethereal IR entry function link.
|
||||
///
|
||||
/// The link represents branching between the deploy and runtime code.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EntryLink {
|
||||
/// The code part type.
|
||||
pub code_type: era_compiler_llvm_context::EraVMCodeType,
|
||||
}
|
||||
|
||||
impl EntryLink {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(code_type: era_compiler_llvm_context::EraVMCodeType) -> Self {
|
||||
Self { code_type }
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for EntryLink
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
let target = context
|
||||
.get_function(EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME)
|
||||
.expect("Always exists")
|
||||
.borrow()
|
||||
.declaration();
|
||||
let is_deploy_code = match self.code_type {
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy => context
|
||||
.integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN)
|
||||
.const_int(1, false),
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime => context
|
||||
.integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN)
|
||||
.const_int(0, false),
|
||||
};
|
||||
context.build_invoke(
|
||||
target,
|
||||
&[is_deploy_code.as_basic_value_enum()],
|
||||
format!("call_link_{}", EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME).as_str(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
//!
|
||||
//! The Ethereal IR block element stack element.
|
||||
//!
|
||||
|
||||
///
|
||||
/// The Ethereal IR block element stack element.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Element {
|
||||
/// The runtime value.
|
||||
Value(String),
|
||||
/// The compile-time value.
|
||||
Constant(num::BigUint),
|
||||
/// The compile-time destination tag.
|
||||
Tag(num::BigUint),
|
||||
/// The compile-time path.
|
||||
Path(String),
|
||||
/// The compile-time hexadecimal data chunk.
|
||||
Data(String),
|
||||
/// The recursive function return address.
|
||||
ReturnAddress(usize),
|
||||
}
|
||||
|
||||
impl Element {
|
||||
pub fn value(identifier: String) -> Self {
|
||||
Self::Value(identifier)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Element {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Value(identifier) => write!(f, "V_{identifier}"),
|
||||
Self::Constant(value) => write!(f, "{value:X}"),
|
||||
Self::Tag(tag) => write!(f, "T_{tag}"),
|
||||
Self::Path(path) => write!(f, "{path}"),
|
||||
Self::Data(data) => write!(f, "{data}"),
|
||||
Self::ReturnAddress(_) => write!(f, "RETURN_ADDRESS"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
//!
|
||||
//! The Ethereal IR block element stack.
|
||||
//!
|
||||
|
||||
pub mod element;
|
||||
|
||||
use self::element::Element;
|
||||
|
||||
///
|
||||
/// The Ethereal IR block element stack.
|
||||
///
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Stack {
|
||||
/// The stack elements.
|
||||
pub elements: Vec<Element>,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
/// The default stack size.
|
||||
pub const DEFAULT_STACK_SIZE: usize = 16;
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
elements: Vec::with_capacity(Self::DEFAULT_STACK_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
elements: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new_with_elements(elements: Vec<Element>) -> Self {
|
||||
Self { elements }
|
||||
}
|
||||
|
||||
///
|
||||
/// The stack state hash, which acts as a block identifier.
|
||||
///
|
||||
/// Each block clone has its own initial stack state, which uniquely identifies the block.
|
||||
///
|
||||
pub fn hash(&self) -> md5::Digest {
|
||||
let mut hash_context = md5::Context::new();
|
||||
for element in self.elements.iter() {
|
||||
match element {
|
||||
Element::Tag(tag) => hash_context.consume(tag.to_bytes_be()),
|
||||
_ => hash_context.consume([0]),
|
||||
}
|
||||
}
|
||||
hash_context.compute()
|
||||
}
|
||||
|
||||
///
|
||||
/// Pushes an element onto the stack.
|
||||
///
|
||||
pub fn push(&mut self, element: Element) {
|
||||
self.elements.push(element);
|
||||
}
|
||||
|
||||
///
|
||||
/// Appends another stack on top of this one.
|
||||
///
|
||||
pub fn append(&mut self, other: &mut Self) {
|
||||
self.elements.append(&mut other.elements);
|
||||
}
|
||||
|
||||
///
|
||||
/// Pops a stack element.
|
||||
///
|
||||
pub fn pop(&mut self) -> anyhow::Result<Element> {
|
||||
self.elements
|
||||
.pop()
|
||||
.ok_or_else(|| anyhow::anyhow!("Stack underflow"))
|
||||
}
|
||||
|
||||
///
|
||||
/// Pops the tag from the top.
|
||||
///
|
||||
pub fn pop_tag(&mut self) -> anyhow::Result<num::BigUint> {
|
||||
match self.elements.pop() {
|
||||
Some(Element::Tag(tag)) => Ok(tag),
|
||||
Some(element) => anyhow::bail!("Expected tag, found {}", element),
|
||||
None => anyhow::bail!("Stack underflow"),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Swaps two stack elements.
|
||||
///
|
||||
pub fn swap(&mut self, index: usize) -> anyhow::Result<()> {
|
||||
if self.elements.len() < index + 1 {
|
||||
anyhow::bail!("Stack underflow");
|
||||
}
|
||||
|
||||
let length = self.elements.len();
|
||||
self.elements.swap(length - 1, length - 1 - index);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Duplicates a stack element.
|
||||
///
|
||||
pub fn dup(&mut self, index: usize) -> anyhow::Result<Element> {
|
||||
if self.elements.len() < index {
|
||||
anyhow::bail!("Stack underflow");
|
||||
}
|
||||
|
||||
Ok(self.elements[self.elements.len() - index].to_owned())
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the stack length.
|
||||
///
|
||||
pub fn len(&self) -> usize {
|
||||
self.elements.len()
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns an emptiness flag.
|
||||
///
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.elements.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Stack {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[ {} ]",
|
||||
self.elements
|
||||
.iter()
|
||||
.map(Element::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(" | ")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
//!
|
||||
//! The Ethereal IR block.
|
||||
//!
|
||||
|
||||
pub mod element;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use num::Zero;
|
||||
|
||||
use crate::evmla::assembly::instruction::name::Name as InstructionName;
|
||||
use crate::evmla::assembly::instruction::Instruction;
|
||||
|
||||
use self::element::stack::Stack as ElementStack;
|
||||
use self::element::Element;
|
||||
|
||||
///
|
||||
/// The Ethereal IR block.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Block {
|
||||
/// The Solidity compiler version.
|
||||
pub solc_version: semver::Version,
|
||||
/// The block key.
|
||||
pub key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The block instance.
|
||||
pub instance: Option<usize>,
|
||||
/// The block elements relevant to the stack consistency.
|
||||
pub elements: Vec<Element>,
|
||||
/// The block predecessors.
|
||||
pub predecessors: HashSet<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>,
|
||||
/// The initial stack state.
|
||||
pub initial_stack: ElementStack,
|
||||
/// The stack.
|
||||
pub stack: ElementStack,
|
||||
/// The extra block hashes for alternative routes.
|
||||
pub extra_hashes: Vec<md5::Digest>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
/// The elements vector initial capacity.
|
||||
pub const ELEMENTS_VECTOR_DEFAULT_CAPACITY: usize = 64;
|
||||
/// The predecessors hashset initial capacity.
|
||||
pub const PREDECESSORS_HASHSET_DEFAULT_CAPACITY: usize = 4;
|
||||
|
||||
///
|
||||
/// Assembles a block from the sequence of instructions.
|
||||
///
|
||||
pub fn try_from_instructions(
|
||||
solc_version: semver::Version,
|
||||
code_type: era_compiler_llvm_context::EraVMCodeType,
|
||||
slice: &[Instruction],
|
||||
) -> anyhow::Result<(Self, usize)> {
|
||||
let mut cursor = 0;
|
||||
|
||||
let tag: num::BigUint = match slice[cursor].name {
|
||||
InstructionName::Tag => {
|
||||
let tag = slice[cursor]
|
||||
.value
|
||||
.as_deref()
|
||||
.expect("Always exists")
|
||||
.parse()
|
||||
.expect("Always valid");
|
||||
cursor += 1;
|
||||
tag
|
||||
}
|
||||
_ => num::BigUint::zero(),
|
||||
};
|
||||
|
||||
let mut block = Self {
|
||||
solc_version: solc_version.clone(),
|
||||
key: era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, tag),
|
||||
instance: None,
|
||||
elements: Vec::with_capacity(Self::ELEMENTS_VECTOR_DEFAULT_CAPACITY),
|
||||
predecessors: HashSet::with_capacity(Self::PREDECESSORS_HASHSET_DEFAULT_CAPACITY),
|
||||
initial_stack: ElementStack::new(),
|
||||
stack: ElementStack::new(),
|
||||
extra_hashes: vec![],
|
||||
};
|
||||
|
||||
let mut dead_code = false;
|
||||
while cursor < slice.len() {
|
||||
if !dead_code {
|
||||
let element: Element = Element::new(solc_version.clone(), slice[cursor].to_owned());
|
||||
block.elements.push(element);
|
||||
}
|
||||
|
||||
match slice[cursor].name {
|
||||
InstructionName::RETURN
|
||||
| InstructionName::REVERT
|
||||
| InstructionName::STOP
|
||||
| InstructionName::INVALID => {
|
||||
cursor += 1;
|
||||
dead_code = true;
|
||||
}
|
||||
InstructionName::JUMP => {
|
||||
cursor += 1;
|
||||
dead_code = true;
|
||||
}
|
||||
InstructionName::Tag => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((block, cursor))
|
||||
}
|
||||
|
||||
///
|
||||
/// Inserts a predecessor tag.
|
||||
///
|
||||
pub fn insert_predecessor(
|
||||
&mut self,
|
||||
key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
instance: usize,
|
||||
) {
|
||||
self.predecessors.insert((key, instance));
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Block
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
context.set_code_type(self.key.code_type);
|
||||
|
||||
for element in self.elements.into_iter() {
|
||||
element.into_llvm(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Block {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"block_{}/{}: {}",
|
||||
self.key,
|
||||
self.instance.unwrap_or_default(),
|
||||
if self.predecessors.is_empty() {
|
||||
"".to_owned()
|
||||
} else {
|
||||
format!(
|
||||
"(predecessors: {})",
|
||||
self.predecessors
|
||||
.iter()
|
||||
.map(|(key, instance)| format!("{}/{}", key, instance))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
},
|
||||
)?;
|
||||
for element in self.elements.iter() {
|
||||
writeln!(f, " {element}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
//!
|
||||
//! The Ethereal IR block queue element.
|
||||
//!
|
||||
|
||||
use crate::evmla::ethereal_ir::function::block::element::stack::Stack;
|
||||
|
||||
///
|
||||
/// The Ethereal IR block queue element.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueueElement {
|
||||
/// The block key.
|
||||
pub block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The block predecessor.
|
||||
pub predecessor: Option<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>,
|
||||
/// The predecessor's last stack state.
|
||||
pub stack: Stack,
|
||||
}
|
||||
|
||||
impl QueueElement {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
predecessor: Option<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>,
|
||||
stack: Stack,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_key,
|
||||
predecessor,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//!
|
||||
//! The Ethereal IR function type.
|
||||
//!
|
||||
|
||||
///
|
||||
/// The Ethereal IR function type.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Type {
|
||||
/// The initial function, combining deploy and runtime code.
|
||||
Initial,
|
||||
/// The recursive function with a specific block starting its recursive context.
|
||||
Recursive {
|
||||
/// The function name.
|
||||
name: String,
|
||||
/// The function initial block key.
|
||||
block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The size of stack input (in cells or 256-bit words).
|
||||
input_size: usize,
|
||||
/// The size of stack output (in cells or 256-bit words).
|
||||
output_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Type {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new_initial() -> Self {
|
||||
Self::Initial
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new_recursive(
|
||||
name: String,
|
||||
block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
input_size: usize,
|
||||
output_size: usize,
|
||||
) -> Self {
|
||||
Self::Recursive {
|
||||
name,
|
||||
block_key,
|
||||
input_size,
|
||||
output_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//!
|
||||
//! The Ethereal IR block visited element.
|
||||
//!
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
///
|
||||
/// The Ethereal IR block visited element.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VisitedElement {
|
||||
/// The block key.
|
||||
pub block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The initial stack state hash.
|
||||
pub stack_hash: md5::Digest,
|
||||
}
|
||||
|
||||
impl VisitedElement {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
stack_hash: md5::Digest,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_key,
|
||||
stack_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VisitedElement {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VisitedElement {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self.block_key.code_type, other.block_key.code_type) {
|
||||
(
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
) => Ordering::Less,
|
||||
(
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
) => Ordering::Greater,
|
||||
(
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
)
|
||||
| (
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
) => {
|
||||
let tag_comparison = self.block_key.tag.cmp(&other.block_key.tag);
|
||||
if tag_comparison == Ordering::Equal {
|
||||
if self.stack_hash == other.stack_hash {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
} else {
|
||||
tag_comparison
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//!
|
||||
//! The Ethereal IR of the EVM bytecode.
|
||||
//!
|
||||
|
||||
pub mod entry_link;
|
||||
pub mod function;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::evmla::assembly::instruction::Instruction;
|
||||
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
|
||||
|
||||
use self::function::block::Block;
|
||||
use self::function::r#type::Type as FunctionType;
|
||||
use self::function::Function;
|
||||
|
||||
///
|
||||
/// The Ethereal IR of the EVM bytecode.
|
||||
///
|
||||
/// The Ethereal IR (EthIR) is a special IR between the EVM legacy assembly and LLVM IR. It is
|
||||
/// created to facilitate the translation and provide an additional environment for applying some
|
||||
/// transformations, duplicating parts of the call and control flow graphs, tracking the
|
||||
/// data flow, and a few more algorithms of static analysis.
|
||||
///
|
||||
/// The most important feature of EthIR is flattening the block tags and duplicating blocks for
|
||||
/// each of initial states of the stack. The LLVM IR supports only static control flow, so the
|
||||
/// stack state must be known all the way throughout the program.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct EtherealIR {
|
||||
/// The Solidity compiler version.
|
||||
pub solc_version: semver::Version,
|
||||
/// The EVMLA extra metadata.
|
||||
pub extra_metadata: ExtraMetadata,
|
||||
/// The all-inlined function.
|
||||
pub entry_function: Function,
|
||||
/// The recursive functions.
|
||||
pub recursive_functions: BTreeMap<era_compiler_llvm_context::EraVMFunctionBlockKey, Function>,
|
||||
}
|
||||
|
||||
impl EtherealIR {
|
||||
/// The default entry function name.
|
||||
pub const DEFAULT_ENTRY_FUNCTION_NAME: &'static str = "main";
|
||||
|
||||
/// The blocks hashmap initial capacity.
|
||||
pub const BLOCKS_HASHMAP_DEFAULT_CAPACITY: usize = 64;
|
||||
|
||||
///
|
||||
/// Assembles a sequence of functions from the sequence of instructions.
|
||||
///
|
||||
pub fn new(
|
||||
solc_version: semver::Version,
|
||||
extra_metadata: ExtraMetadata,
|
||||
blocks: HashMap<era_compiler_llvm_context::EraVMFunctionBlockKey, Block>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut entry_function = Function::new(solc_version.clone(), FunctionType::new_initial());
|
||||
let mut recursive_functions = BTreeMap::new();
|
||||
let mut visited_functions = BTreeSet::new();
|
||||
entry_function.traverse(
|
||||
&blocks,
|
||||
&mut recursive_functions,
|
||||
&extra_metadata,
|
||||
&mut visited_functions,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
solc_version,
|
||||
extra_metadata,
|
||||
entry_function,
|
||||
recursive_functions,
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Gets blocks for the specified type of the contract code.
|
||||
///
|
||||
pub fn get_blocks(
|
||||
solc_version: semver::Version,
|
||||
code_type: era_compiler_llvm_context::EraVMCodeType,
|
||||
instructions: &[Instruction],
|
||||
) -> anyhow::Result<HashMap<era_compiler_llvm_context::EraVMFunctionBlockKey, Block>> {
|
||||
let mut blocks = HashMap::with_capacity(Self::BLOCKS_HASHMAP_DEFAULT_CAPACITY);
|
||||
let mut offset = 0;
|
||||
|
||||
while offset < instructions.len() {
|
||||
let (block, size) = Block::try_from_instructions(
|
||||
solc_version.clone(),
|
||||
code_type,
|
||||
&instructions[offset..],
|
||||
)?;
|
||||
blocks.insert(
|
||||
era_compiler_llvm_context::EraVMFunctionBlockKey::new(
|
||||
code_type,
|
||||
block.key.tag.clone(),
|
||||
),
|
||||
block,
|
||||
);
|
||||
offset += size;
|
||||
}
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for EtherealIR
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.entry_function.declare(context)?;
|
||||
|
||||
for (_key, function) in self.recursive_functions.iter_mut() {
|
||||
function.declare(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
context.evmla_mut().stack = vec![];
|
||||
|
||||
self.entry_function.into_llvm(context)?;
|
||||
|
||||
for (_key, function) in self.recursive_functions.into_iter() {
|
||||
function.into_llvm(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EtherealIR {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.entry_function)?;
|
||||
|
||||
for (_key, function) in self.recursive_functions.iter() {
|
||||
writeln!(f, "{}", function)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//!
|
||||
//! The EVM legacy assembly compiling tools.
|
||||
//!
|
||||
|
||||
pub mod assembly;
|
||||
pub mod ethereal_ir;
|
||||
@@ -0,0 +1,396 @@
|
||||
//!
|
||||
//! Solidity to EraVM compiler library.
|
||||
//!
|
||||
|
||||
pub(crate) mod build;
|
||||
pub(crate) mod r#const;
|
||||
pub(crate) mod evmla;
|
||||
pub(crate) mod missing_libraries;
|
||||
pub(crate) mod process;
|
||||
pub(crate) mod project;
|
||||
pub(crate) mod solc;
|
||||
pub(crate) mod warning;
|
||||
pub(crate) mod yul;
|
||||
|
||||
pub use self::build::contract::Contract as ContractBuild;
|
||||
pub use self::build::Build;
|
||||
pub use self::missing_libraries::MissingLibraries;
|
||||
pub use self::process::input::Input as ProcessInput;
|
||||
pub use self::process::output::Output as ProcessOutput;
|
||||
pub use self::process::run as run_process;
|
||||
pub use self::process::EXECUTABLE;
|
||||
pub use self::project::contract::Contract as ProjectContract;
|
||||
pub use self::project::Project;
|
||||
pub use self::r#const::*;
|
||||
pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract;
|
||||
pub use self::solc::combined_json::CombinedJson as SolcCombinedJson;
|
||||
pub use self::solc::pipeline::Pipeline as SolcPipeline;
|
||||
pub use self::solc::standard_json::input::language::Language as SolcStandardJsonInputLanguage;
|
||||
pub use self::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
|
||||
pub use self::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
|
||||
pub use self::solc::standard_json::input::settings::selection::file::flag::Flag as SolcStandardJsonInputSettingsSelectionFileFlag;
|
||||
pub use self::solc::standard_json::input::settings::selection::file::File as SolcStandardJsonInputSettingsSelectionFile;
|
||||
pub use self::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
|
||||
pub use self::solc::standard_json::input::settings::Settings as SolcStandardJsonInputSettings;
|
||||
pub use self::solc::standard_json::input::source::Source as SolcStandardJsonInputSource;
|
||||
pub use self::solc::standard_json::input::Input as SolcStandardJsonInput;
|
||||
pub use self::solc::standard_json::output::contract::evm::bytecode::Bytecode as SolcStandardJsonOutputContractEVMBytecode;
|
||||
pub use self::solc::standard_json::output::contract::evm::EVM as SolcStandardJsonOutputContractEVM;
|
||||
pub use self::solc::standard_json::output::contract::Contract as SolcStandardJsonOutputContract;
|
||||
pub use self::solc::standard_json::output::Output as SolcStandardJsonOutput;
|
||||
pub use self::solc::version::Version as SolcVersion;
|
||||
pub use self::solc::Compiler as SolcCompiler;
|
||||
pub use self::warning::Warning;
|
||||
|
||||
pub mod test_utils;
|
||||
pub mod tests;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
///
|
||||
/// Runs the Yul mode.
|
||||
///
|
||||
pub fn yul(
|
||||
input_files: &[PathBuf],
|
||||
solc: &mut SolcCompiler,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
is_system_mode: bool,
|
||||
include_metadata_hash: bool,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> anyhow::Result<Build> {
|
||||
let path = match input_files.len() {
|
||||
1 => input_files.first().expect("Always exists"),
|
||||
0 => anyhow::bail!("The input file is missing"),
|
||||
length => anyhow::bail!(
|
||||
"Only one input file is allowed in the Yul mode, but found {}",
|
||||
length,
|
||||
),
|
||||
};
|
||||
|
||||
let solc_validator = if is_system_mode {
|
||||
None
|
||||
} else {
|
||||
if solc.version()?.default != SolcCompiler::LAST_SUPPORTED_VERSION {
|
||||
anyhow::bail!(
|
||||
"The Yul mode is only supported with the most recent version of the Solidity compiler: {}",
|
||||
SolcCompiler::LAST_SUPPORTED_VERSION,
|
||||
);
|
||||
}
|
||||
|
||||
Some(&*solc)
|
||||
};
|
||||
|
||||
let project = Project::try_from_yul_path(path, solc_validator)?;
|
||||
|
||||
let build = project.compile(
|
||||
optimizer_settings,
|
||||
is_system_mode,
|
||||
include_metadata_hash,
|
||||
false,
|
||||
debug_config,
|
||||
)?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
|
||||
///
|
||||
/// Runs the LLVM IR mode.
|
||||
///
|
||||
pub fn llvm_ir(
|
||||
input_files: &[PathBuf],
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
is_system_mode: bool,
|
||||
include_metadata_hash: bool,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> anyhow::Result<Build> {
|
||||
let path = match input_files.len() {
|
||||
1 => input_files.first().expect("Always exists"),
|
||||
0 => anyhow::bail!("The input file is missing"),
|
||||
length => anyhow::bail!(
|
||||
"Only one input file is allowed in the LLVM IR mode, but found {}",
|
||||
length,
|
||||
),
|
||||
};
|
||||
|
||||
let project = Project::try_from_llvm_ir_path(path)?;
|
||||
|
||||
let build = project.compile(
|
||||
optimizer_settings,
|
||||
is_system_mode,
|
||||
include_metadata_hash,
|
||||
false,
|
||||
debug_config,
|
||||
)?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
|
||||
///
|
||||
/// Runs the EraVM assembly mode.
|
||||
///
|
||||
pub fn zkasm(
|
||||
input_files: &[PathBuf],
|
||||
include_metadata_hash: bool,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> anyhow::Result<Build> {
|
||||
let path = match input_files.len() {
|
||||
1 => input_files.first().expect("Always exists"),
|
||||
0 => anyhow::bail!("The input file is missing"),
|
||||
length => anyhow::bail!(
|
||||
"Only one input file is allowed in the EraVM assembly mode, but found {}",
|
||||
length,
|
||||
),
|
||||
};
|
||||
|
||||
let project = Project::try_from_zkasm_path(path)?;
|
||||
|
||||
let optimizer_settings = era_compiler_llvm_context::OptimizerSettings::none();
|
||||
let build = project.compile(
|
||||
optimizer_settings,
|
||||
false,
|
||||
include_metadata_hash,
|
||||
false,
|
||||
debug_config,
|
||||
)?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
|
||||
///
|
||||
/// Runs the standard output mode.
|
||||
///
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn standard_output(
|
||||
input_files: &[PathBuf],
|
||||
libraries: Vec<String>,
|
||||
solc: &mut SolcCompiler,
|
||||
evm_version: Option<era_compiler_common::EVMVersion>,
|
||||
solc_optimizer_enabled: bool,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
force_evmla: bool,
|
||||
is_system_mode: bool,
|
||||
include_metadata_hash: bool,
|
||||
base_path: Option<String>,
|
||||
include_paths: Vec<String>,
|
||||
allow_paths: Option<String>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
suppressed_warnings: Option<Vec<Warning>>,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> anyhow::Result<Build> {
|
||||
let solc_version = solc.version()?;
|
||||
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
|
||||
|
||||
let solc_input = SolcStandardJsonInput::try_from_paths(
|
||||
SolcStandardJsonInputLanguage::Solidity,
|
||||
evm_version,
|
||||
input_files,
|
||||
libraries,
|
||||
remappings,
|
||||
SolcStandardJsonInputSettingsSelection::new_required(solc_pipeline),
|
||||
SolcStandardJsonInputSettingsOptimizer::new(
|
||||
solc_optimizer_enabled,
|
||||
None,
|
||||
&solc_version.default,
|
||||
optimizer_settings.is_fallback_to_size_enabled(),
|
||||
optimizer_settings.is_system_request_memoization_disabled(),
|
||||
),
|
||||
None,
|
||||
solc_pipeline == SolcPipeline::Yul,
|
||||
suppressed_warnings,
|
||||
)?;
|
||||
|
||||
let source_code_files = solc_input
|
||||
.sources
|
||||
.iter()
|
||||
.map(|(path, source)| (path.to_owned(), source.content.to_owned()))
|
||||
.collect();
|
||||
|
||||
let libraries = solc_input.settings.libraries.clone().unwrap_or_default();
|
||||
let mut solc_output = solc.standard_json(
|
||||
solc_input,
|
||||
solc_pipeline,
|
||||
base_path,
|
||||
include_paths,
|
||||
allow_paths,
|
||||
)?;
|
||||
|
||||
if let Some(errors) = solc_output.errors.as_deref() {
|
||||
let mut has_errors = false;
|
||||
|
||||
for error in errors.iter() {
|
||||
if error.severity.as_str() == "error" {
|
||||
has_errors = true;
|
||||
}
|
||||
|
||||
eprintln!("{error}");
|
||||
}
|
||||
|
||||
if has_errors {
|
||||
anyhow::bail!("Error(s) found. Compilation aborted");
|
||||
}
|
||||
}
|
||||
|
||||
let project = solc_output.try_to_project(
|
||||
source_code_files,
|
||||
libraries,
|
||||
solc_pipeline,
|
||||
&solc_version,
|
||||
debug_config.as_ref(),
|
||||
)?;
|
||||
|
||||
let build = project.compile(
|
||||
optimizer_settings,
|
||||
is_system_mode,
|
||||
include_metadata_hash,
|
||||
false,
|
||||
debug_config,
|
||||
)?;
|
||||
|
||||
Ok(build)
|
||||
}
|
||||
|
||||
///
|
||||
/// Runs the standard JSON mode.
|
||||
///
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn standard_json(
|
||||
solc: &mut SolcCompiler,
|
||||
detect_missing_libraries: bool,
|
||||
force_evmla: bool,
|
||||
is_system_mode: bool,
|
||||
base_path: Option<String>,
|
||||
include_paths: Vec<String>,
|
||||
allow_paths: Option<String>,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> anyhow::Result<()> {
|
||||
let solc_version = solc.version()?;
|
||||
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
|
||||
let zksolc_version = semver::Version::parse(env!("CARGO_PKG_VERSION")).expect("Always valid");
|
||||
|
||||
let solc_input = SolcStandardJsonInput::try_from_stdin(solc_pipeline)?;
|
||||
let source_code_files = solc_input
|
||||
.sources
|
||||
.iter()
|
||||
.map(|(path, source)| (path.to_owned(), source.content.to_owned()))
|
||||
.collect();
|
||||
|
||||
let optimizer_settings =
|
||||
era_compiler_llvm_context::OptimizerSettings::try_from(&solc_input.settings.optimizer)?;
|
||||
|
||||
let include_metadata_hash = match solc_input.settings.metadata {
|
||||
Some(ref metadata) => {
|
||||
metadata.bytecode_hash != Some(era_compiler_llvm_context::EraVMMetadataHash::None)
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
let libraries = solc_input.settings.libraries.clone().unwrap_or_default();
|
||||
let mut solc_output = solc.standard_json(
|
||||
solc_input,
|
||||
solc_pipeline,
|
||||
base_path,
|
||||
include_paths,
|
||||
allow_paths,
|
||||
)?;
|
||||
|
||||
if let Some(errors) = solc_output.errors.as_deref() {
|
||||
for error in errors.iter() {
|
||||
if error.severity.as_str() == "error" {
|
||||
serde_json::to_writer(std::io::stdout(), &solc_output)?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let project = solc_output.try_to_project(
|
||||
source_code_files,
|
||||
libraries,
|
||||
solc_pipeline,
|
||||
&solc_version,
|
||||
debug_config.as_ref(),
|
||||
)?;
|
||||
|
||||
if detect_missing_libraries {
|
||||
let missing_libraries = project.get_missing_libraries();
|
||||
missing_libraries.write_to_standard_json(
|
||||
&mut solc_output,
|
||||
&solc_version,
|
||||
&zksolc_version,
|
||||
)?;
|
||||
} else {
|
||||
let build = project.compile(
|
||||
optimizer_settings,
|
||||
is_system_mode,
|
||||
include_metadata_hash,
|
||||
false,
|
||||
debug_config,
|
||||
)?;
|
||||
build.write_to_standard_json(&mut solc_output, &solc_version, &zksolc_version)?;
|
||||
}
|
||||
serde_json::to_writer(std::io::stdout(), &solc_output)?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
///
|
||||
/// Runs the combined JSON mode.
|
||||
///
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn combined_json(
|
||||
format: String,
|
||||
input_files: &[PathBuf],
|
||||
libraries: Vec<String>,
|
||||
solc: &mut SolcCompiler,
|
||||
evm_version: Option<era_compiler_common::EVMVersion>,
|
||||
solc_optimizer_enabled: bool,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
force_evmla: bool,
|
||||
is_system_mode: bool,
|
||||
include_metadata_hash: bool,
|
||||
base_path: Option<String>,
|
||||
include_paths: Vec<String>,
|
||||
allow_paths: Option<String>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
suppressed_warnings: Option<Vec<Warning>>,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
output_directory: Option<PathBuf>,
|
||||
overwrite: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let zksolc_version = semver::Version::parse(env!("CARGO_PKG_VERSION")).expect("Always valid");
|
||||
|
||||
let build = standard_output(
|
||||
input_files,
|
||||
libraries,
|
||||
solc,
|
||||
evm_version,
|
||||
solc_optimizer_enabled,
|
||||
optimizer_settings,
|
||||
force_evmla,
|
||||
is_system_mode,
|
||||
include_metadata_hash,
|
||||
base_path,
|
||||
include_paths,
|
||||
allow_paths,
|
||||
remappings,
|
||||
suppressed_warnings,
|
||||
debug_config,
|
||||
)?;
|
||||
|
||||
let mut combined_json = solc.combined_json(input_files, format.as_str())?;
|
||||
build.write_to_combined_json(&mut combined_json, &zksolc_version)?;
|
||||
|
||||
match output_directory {
|
||||
Some(output_directory) => {
|
||||
std::fs::create_dir_all(output_directory.as_path())?;
|
||||
|
||||
combined_json.write_to_directory(output_directory.as_path(), overwrite)?;
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string(&combined_json).expect("Always valid")
|
||||
);
|
||||
}
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//!
|
||||
//! The missing Solidity libraries.
|
||||
//!
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::solc::standard_json::output::Output as StandardJsonOutput;
|
||||
use crate::solc::version::Version as SolcVersion;
|
||||
|
||||
///
|
||||
/// The missing Solidity libraries.
|
||||
///
|
||||
pub struct MissingLibraries {
|
||||
/// The missing libraries.
|
||||
pub contract_libraries: BTreeMap<String, HashSet<String>>,
|
||||
}
|
||||
|
||||
impl MissingLibraries {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(contract_libraries: BTreeMap<String, HashSet<String>>) -> Self {
|
||||
Self { contract_libraries }
|
||||
}
|
||||
|
||||
///
|
||||
/// Writes the missing libraries to the standard JSON.
|
||||
///
|
||||
pub fn write_to_standard_json(
|
||||
mut self,
|
||||
standard_json: &mut StandardJsonOutput,
|
||||
solc_version: &SolcVersion,
|
||||
zksolc_version: &semver::Version,
|
||||
) -> anyhow::Result<()> {
|
||||
let contracts = match standard_json.contracts.as_mut() {
|
||||
Some(contracts) => contracts,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
for (path, contracts) in contracts.iter_mut() {
|
||||
for (name, contract) in contracts.iter_mut() {
|
||||
let full_name = format!("{path}:{name}");
|
||||
let missing_libraries = self.contract_libraries.remove(full_name.as_str());
|
||||
|
||||
if let Some(missing_libraries) = missing_libraries {
|
||||
contract.missing_libraries = Some(missing_libraries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
standard_json.version = Some(solc_version.default.to_string());
|
||||
standard_json.long_version = Some(solc_version.long.to_owned());
|
||||
standard_json.zk_version = Some(zksolc_version.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//!
|
||||
//! Process for compiling a single compilation unit.
|
||||
//!
|
||||
//! The input data.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::project::contract::Contract;
|
||||
use crate::project::Project;
|
||||
|
||||
///
|
||||
/// The input data.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Input {
|
||||
/// The contract representation.
|
||||
pub contract: Contract,
|
||||
/// The project representation.
|
||||
pub project: Project,
|
||||
/// The system mode flag.
|
||||
pub is_system_mode: bool,
|
||||
/// Whether to append the metadata hash.
|
||||
pub include_metadata_hash: bool,
|
||||
/// Enables the test bytecode encoding.
|
||||
pub enable_test_encoding: bool,
|
||||
/// The optimizer settings.
|
||||
pub optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
/// The debug output config.
|
||||
pub debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
contract: Contract,
|
||||
project: Project,
|
||||
is_system_mode: bool,
|
||||
include_metadata_hash: bool,
|
||||
enable_test_encoding: bool,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> Self {
|
||||
Self {
|
||||
contract,
|
||||
project,
|
||||
is_system_mode,
|
||||
include_metadata_hash,
|
||||
enable_test_encoding,
|
||||
optimizer_settings,
|
||||
debug_config,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//!
|
||||
//! Process for compiling a single compilation unit.
|
||||
//!
|
||||
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use self::input::Input;
|
||||
use self::output::Output;
|
||||
|
||||
/// The overriden executable name used when the compiler is run as a library.
|
||||
pub static EXECUTABLE: OnceCell<PathBuf> = OnceCell::new();
|
||||
|
||||
///
|
||||
/// Read input from `stdin`, compile a contract, and write the output to `stdout`.
|
||||
///
|
||||
pub fn run() -> anyhow::Result<()> {
|
||||
let mut stdin = std::io::stdin();
|
||||
let mut stdout = std::io::stdout();
|
||||
let mut stderr = std::io::stderr();
|
||||
|
||||
let mut buffer = Vec::with_capacity(16384);
|
||||
stdin.read_to_end(&mut buffer).expect("Stdin reading error");
|
||||
|
||||
let input: Input = era_compiler_common::deserialize_from_slice(buffer.as_slice())?;
|
||||
if input.enable_test_encoding {
|
||||
todo!()
|
||||
}
|
||||
let result = input.contract.compile(
|
||||
input.project,
|
||||
input.optimizer_settings,
|
||||
input.is_system_mode,
|
||||
input.include_metadata_hash,
|
||||
input.debug_config,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(build) => {
|
||||
let output = Output::new(build);
|
||||
let json = serde_json::to_vec(&output).expect("Always valid");
|
||||
stdout
|
||||
.write_all(json.as_slice())
|
||||
.expect("Stdout writing error");
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
let message = error.to_string();
|
||||
stderr
|
||||
.write_all(message.as_bytes())
|
||||
.expect("Stderr writing error");
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Runs this process recursively to compile a single contract.
|
||||
///
|
||||
pub fn call(input: Input) -> anyhow::Result<Output> {
|
||||
let input_json = serde_json::to_vec(&input).expect("Always valid");
|
||||
|
||||
let executable = match EXECUTABLE.get() {
|
||||
Some(executable) => executable.to_owned(),
|
||||
None => std::env::current_exe()?,
|
||||
};
|
||||
|
||||
let mut command = Command::new(executable.as_path());
|
||||
command.stdin(std::process::Stdio::piped());
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
command.stderr(std::process::Stdio::piped());
|
||||
command.arg("--recursive-process");
|
||||
let process = command.spawn().map_err(|error| {
|
||||
anyhow::anyhow!("{:?} subprocess spawning error: {:?}", executable, error)
|
||||
})?;
|
||||
|
||||
process
|
||||
.stdin
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("{:?} stdin getting error", executable))?
|
||||
.write_all(input_json.as_slice())
|
||||
.map_err(|error| anyhow::anyhow!("{:?} stdin writing error: {:?}", executable, error))?;
|
||||
let output = process.wait_with_output().map_err(|error| {
|
||||
anyhow::anyhow!("{:?} subprocess output error: {:?}", executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{}",
|
||||
String::from_utf8_lossy(output.stderr.as_slice()).to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let output: Output = era_compiler_common::deserialize_from_slice(output.stdout.as_slice())
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"{:?} subprocess output parsing error: {}",
|
||||
executable,
|
||||
error,
|
||||
)
|
||||
})?;
|
||||
Ok(output)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//!
|
||||
//! Process for compiling a single compilation unit.
|
||||
//!
|
||||
//! The output data.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::build::contract::Contract as ContractBuild;
|
||||
|
||||
///
|
||||
/// The output data.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Output {
|
||||
/// The contract build.
|
||||
pub build: ContractBuild,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(build: ContractBuild) -> Self {
|
||||
Self { build }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//!
|
||||
//! The contract EVM legacy assembly source code.
|
||||
//!
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::evmla::assembly::Assembly;
|
||||
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
|
||||
|
||||
///
|
||||
/// The contract EVM legacy assembly source code.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct EVMLA {
|
||||
/// The EVM legacy assembly source code.
|
||||
pub assembly: Assembly,
|
||||
}
|
||||
|
||||
impl EVMLA {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(mut assembly: Assembly, extra_metadata: ExtraMetadata) -> Self {
|
||||
assembly.extra_metadata = Some(extra_metadata);
|
||||
Self { assembly }
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
self.assembly.get_missing_libraries()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for EVMLA
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.assembly.declare(context)
|
||||
}
|
||||
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.assembly.into_llvm(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//!
|
||||
//! 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 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
//!
|
||||
//! The contract source code.
|
||||
//!
|
||||
|
||||
pub mod evmla;
|
||||
pub mod llvm_ir;
|
||||
pub mod yul;
|
||||
pub mod zkasm;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::evmla::assembly::Assembly;
|
||||
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
|
||||
use crate::yul::parser::statement::object::Object;
|
||||
|
||||
use self::evmla::EVMLA;
|
||||
use self::llvm_ir::LLVMIR;
|
||||
use self::yul::Yul;
|
||||
use self::zkasm::ZKASM;
|
||||
|
||||
///
|
||||
/// The contract source code.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum IR {
|
||||
/// The Yul source code.
|
||||
Yul(Yul),
|
||||
/// The EVM legacy assembly source code.
|
||||
EVMLA(EVMLA),
|
||||
/// The LLVM IR source code.
|
||||
LLVMIR(LLVMIR),
|
||||
/// The EraVM assembly source code.
|
||||
ZKASM(ZKASM),
|
||||
}
|
||||
|
||||
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_evmla(assembly: Assembly, extra_metadata: ExtraMetadata) -> Self {
|
||||
Self::EVMLA(EVMLA::new(assembly, extra_metadata))
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new_llvm_ir(path: String, source: String) -> Self {
|
||||
Self::LLVMIR(LLVMIR::new(path, source))
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new_zkasm(path: String, source: String) -> Self {
|
||||
Self::ZKASM(ZKASM::new(path, source))
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
match self {
|
||||
Self::Yul(inner) => inner.get_missing_libraries(),
|
||||
Self::EVMLA(inner) => inner.get_missing_libraries(),
|
||||
Self::LLVMIR(_inner) => HashSet::new(),
|
||||
Self::ZKASM(_inner) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for IR
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Self::Yul(inner) => inner.declare(context),
|
||||
Self::EVMLA(inner) => inner.declare(context),
|
||||
Self::LLVMIR(_inner) => Ok(()),
|
||||
Self::ZKASM(_inner) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Self::Yul(inner) => inner.into_llvm(context),
|
||||
Self::EVMLA(inner) => inner.into_llvm(context),
|
||||
Self::LLVMIR(_inner) => Ok(()),
|
||||
Self::ZKASM(_inner) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//!
|
||||
//! The contract Yul source code.
|
||||
//!
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::yul::parser::statement::object::Object;
|
||||
|
||||
///
|
||||
/// The 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,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
self.object.get_missing_libraries()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Yul
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.object.declare(context)
|
||||
}
|
||||
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.object.into_llvm(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//!
|
||||
//! The contract EraVM assembly source code.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The contract EraVM assembly source code.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct ZKASM {
|
||||
/// The EraVM assembly file path.
|
||||
pub path: String,
|
||||
/// The EraVM assembly source code.
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
impl ZKASM {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(path: String, source: String) -> Self {
|
||||
Self { path, source }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//!
|
||||
//! The Solidity contract metadata.
|
||||
//!
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The Solidity contract metadata.
|
||||
///
|
||||
/// Is used to append the metadata hash to the contract bytecode.
|
||||
///
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Metadata {
|
||||
/// The `solc` metadata.
|
||||
pub solc_metadata: serde_json::Value,
|
||||
/// The `solc` version.
|
||||
pub solc_version: semver::Version,
|
||||
/// The zkVM `solc` edition.
|
||||
pub solc_zkvm_edition: Option<semver::Version>,
|
||||
/// The EraVM compiler version.
|
||||
pub zk_version: semver::Version,
|
||||
/// The EraVM compiler optimizer settings.
|
||||
pub optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
solc_metadata: serde_json::Value,
|
||||
solc_version: semver::Version,
|
||||
solc_zkvm_edition: Option<semver::Version>,
|
||||
zk_version: semver::Version,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
) -> Self {
|
||||
Self {
|
||||
solc_metadata,
|
||||
solc_version,
|
||||
solc_zkvm_edition,
|
||||
zk_version,
|
||||
optimizer_settings,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
//!
|
||||
//! The contract data.
|
||||
//!
|
||||
|
||||
pub mod ir;
|
||||
pub mod metadata;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha3::Digest;
|
||||
|
||||
use era_compiler_llvm_context::EraVMWriteLLVM;
|
||||
|
||||
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;
|
||||
|
||||
///
|
||||
/// The contract data.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Contract {
|
||||
/// The absolute file path.
|
||||
pub path: String,
|
||||
/// The IR source code data.
|
||||
pub ir: IR,
|
||||
/// The metadata JSON.
|
||||
pub metadata_json: serde_json::Value,
|
||||
}
|
||||
|
||||
impl Contract {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
path: String,
|
||||
source_hash: [u8; era_compiler_common::BYTE_LENGTH_FIELD],
|
||||
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"),
|
||||
})
|
||||
});
|
||||
|
||||
Self {
|
||||
path,
|
||||
ir,
|
||||
metadata_json,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the contract identifier, which is:
|
||||
/// - the Yul object identifier for Yul
|
||||
/// - the full contract path for EVM legacy assembly
|
||||
/// - the module name for LLVM IR
|
||||
///
|
||||
pub fn identifier(&self) -> &str {
|
||||
match self.ir {
|
||||
IR::Yul(ref yul) => yul.object.identifier.as_str(),
|
||||
IR::EVMLA(ref evm) => evm.assembly.full_path(),
|
||||
IR::LLVMIR(ref llvm_ir) => llvm_ir.path.as_str(),
|
||||
IR::ZKASM(ref zkasm) => zkasm.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::EVMLA(ref mut evm) => evm.assembly.factory_dependencies.drain().collect(),
|
||||
IR::LLVMIR(_) => HashSet::new(),
|
||||
IR::ZKASM(_) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Compiles the specified contract, setting its build artifacts.
|
||||
///
|
||||
pub fn compile(
|
||||
mut self,
|
||||
project: Project,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
is_system_mode: bool,
|
||||
include_metadata_hash: bool,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> anyhow::Result<ContractBuild> {
|
||||
let llvm = inkwell::context::Context::create();
|
||||
let optimizer = era_compiler_llvm_context::Optimizer::new(optimizer_settings);
|
||||
|
||||
let version = project.version.clone();
|
||||
let identifier = self.identifier().to_owned();
|
||||
|
||||
let metadata = Metadata::new(
|
||||
self.metadata_json.take(),
|
||||
version.default.clone(),
|
||||
version.l2_revision.clone(),
|
||||
semver::Version::parse(env!("CARGO_PKG_VERSION")).expect("Always valid"),
|
||||
optimizer.settings().to_owned(),
|
||||
);
|
||||
let metadata_json = serde_json::to_value(&metadata).expect("Always valid");
|
||||
let metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]> =
|
||||
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 module = match self.ir {
|
||||
IR::LLVMIR(ref llvm_ir) => {
|
||||
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()))?
|
||||
}
|
||||
IR::ZKASM(ref zkasm) => {
|
||||
let build = era_compiler_llvm_context::eravm_build_assembly_text(
|
||||
self.path.as_str(),
|
||||
zkasm.source.as_str(),
|
||||
metadata_hash,
|
||||
debug_config.as_ref(),
|
||||
)?;
|
||||
return Ok(ContractBuild::new(
|
||||
self.path,
|
||||
identifier,
|
||||
build,
|
||||
metadata_json,
|
||||
HashSet::new(),
|
||||
));
|
||||
}
|
||||
_ => llvm.create_module(self.path.as_str()),
|
||||
};
|
||||
let mut context = era_compiler_llvm_context::EraVMContext::new(
|
||||
&llvm,
|
||||
module,
|
||||
optimizer,
|
||||
Some(project),
|
||||
include_metadata_hash,
|
||||
debug_config,
|
||||
);
|
||||
context.set_solidity_data(era_compiler_llvm_context::EraVMContextSolidityData::default());
|
||||
match self.ir {
|
||||
IR::Yul(_) => {
|
||||
let yul_data = era_compiler_llvm_context::EraVMContextYulData::new(is_system_mode);
|
||||
context.set_yul_data(yul_data);
|
||||
}
|
||||
IR::EVMLA(_) => {
|
||||
let evmla_data =
|
||||
era_compiler_llvm_context::EraVMContextEVMLAData::new(version.default);
|
||||
context.set_evmla_data(evmla_data);
|
||||
}
|
||||
IR::LLVMIR(_) => {}
|
||||
IR::ZKASM(_) => {}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
})?;
|
||||
|
||||
let build = context.build(self.path.as_str(), metadata_hash)?;
|
||||
|
||||
Ok(ContractBuild::new(
|
||||
self.path,
|
||||
identifier,
|
||||
build,
|
||||
metadata_json,
|
||||
factory_dependencies,
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
self.ir.get_missing_libraries()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> EraVMWriteLLVM<D> for Contract
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.ir.declare(context)
|
||||
}
|
||||
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.ir.into_llvm(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
//!
|
||||
//! The processed input data.
|
||||
//!
|
||||
|
||||
pub mod contract;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
|
||||
use rayon::iter::IntoParallelIterator;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha3::Digest;
|
||||
|
||||
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::project::contract::ir::IR;
|
||||
use crate::solc::version::Version as SolcVersion;
|
||||
use crate::solc::Compiler as SolcCompiler;
|
||||
use crate::yul::lexer::Lexer;
|
||||
use crate::yul::parser::statement::object::Object;
|
||||
|
||||
use self::contract::Contract;
|
||||
|
||||
///
|
||||
/// The processes input data.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Project {
|
||||
/// The source code version.
|
||||
pub version: 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>>,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
version: SolcVersion,
|
||||
contracts: BTreeMap<String, Contract>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
) -> Self {
|
||||
let mut identifier_paths = BTreeMap::new();
|
||||
for (path, contract) in contracts.iter() {
|
||||
identifier_paths.insert(contract.identifier().to_owned(), path.to_owned());
|
||||
}
|
||||
|
||||
Self {
|
||||
version,
|
||||
contracts,
|
||||
identifier_paths,
|
||||
libraries,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Compiles all contracts, returning their build artifacts.
|
||||
///
|
||||
pub fn compile(
|
||||
self,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
is_system_mode: bool,
|
||||
include_metadata_hash: bool,
|
||||
bytecode_encoding_testing: bool,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> anyhow::Result<Build> {
|
||||
let project = self.clone();
|
||||
let results: BTreeMap<String, anyhow::Result<ContractBuild>> = self
|
||||
.contracts
|
||||
.into_par_iter()
|
||||
.map(|(full_path, contract)| {
|
||||
let process_output = crate::process::call(ProcessInput::new(
|
||||
contract,
|
||||
project.clone(),
|
||||
is_system_mode,
|
||||
include_metadata_hash,
|
||||
bytecode_encoding_testing,
|
||||
optimizer_settings.clone(),
|
||||
debug_config.clone(),
|
||||
));
|
||||
|
||||
(full_path, process_output.map(|output| output.build))
|
||||
})
|
||||
.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)
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> MissingLibraries {
|
||||
let deployed_libraries = self
|
||||
.libraries
|
||||
.iter()
|
||||
.flat_map(|(file, names)| {
|
||||
names
|
||||
.iter()
|
||||
.map(|(name, _address)| format!("{file}:{name}"))
|
||||
.collect::<HashSet<String>>()
|
||||
})
|
||||
.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)
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses the Yul source code file and returns the source data.
|
||||
///
|
||||
pub fn try_from_yul_path(
|
||||
path: &Path,
|
||||
solc_validator: Option<&SolcCompiler>,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses the test Yul source code string and returns the source data.
|
||||
///
|
||||
/// Only for integration testing purposes.
|
||||
///
|
||||
pub fn try_from_yul_string(
|
||||
path: &Path,
|
||||
source_code: &str,
|
||||
solc_validator: Option<&SolcCompiler>,
|
||||
) -> anyhow::Result<Self> {
|
||||
if let Some(solc) = solc_validator {
|
||||
solc.validate_yul(path)?;
|
||||
}
|
||||
|
||||
let source_version = SolcVersion::new_simple(SolcCompiler::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(era_compiler_llvm_context::eravm_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(),
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses the EraVM assembly source code file and returns the source data.
|
||||
///
|
||||
pub fn try_from_zkasm_path(path: &Path) -> anyhow::Result<Self> {
|
||||
let source_code = std::fs::read_to_string(path).map_err(|error| {
|
||||
anyhow::anyhow!("EraVM assembly file {:?} reading error: {}", path, error)
|
||||
})?;
|
||||
let source_hash = sha3::Keccak256::digest(source_code.as_bytes()).into();
|
||||
|
||||
let source_version =
|
||||
SolcVersion::new_simple(era_compiler_llvm_context::eravm_const::ZKEVM_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_zkasm(path, source_code),
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
Ok(Self::new(
|
||||
source_version,
|
||||
project_contracts,
|
||||
BTreeMap::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl era_compiler_llvm_context::EraVMDependency for Project {
|
||||
fn compile(
|
||||
project: Self,
|
||||
identifier: &str,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
is_system_mode: bool,
|
||||
include_metadata_hash: bool,
|
||||
debug_config: Option<era_compiler_llvm_context::DebugConfig>,
|
||||
) -> 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,
|
||||
is_system_mode,
|
||||
include_metadata_hash,
|
||||
debug_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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//!
|
||||
//! The `solc --combined-json` contract.
|
||||
//!
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The contract.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Contract {
|
||||
/// The `solc` hashes output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hashes: Option<BTreeMap<String, String>>,
|
||||
/// The `solc` ABI output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub abi: Option<serde_json::Value>,
|
||||
/// The `solc` metadata output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<String>,
|
||||
/// The `solc` developer documentation output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub devdoc: Option<serde_json::Value>,
|
||||
/// The `solc` user documentation output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub userdoc: Option<serde_json::Value>,
|
||||
/// The `solc` storage layout output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub storage_layout: Option<serde_json::Value>,
|
||||
/// The `solc` AST output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ast: Option<serde_json::Value>,
|
||||
/// The `solc` assembly output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub asm: Option<serde_json::Value>,
|
||||
/// The `solc` hexadecimal binary output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bin: Option<String>,
|
||||
/// The `solc` hexadecimal binary runtime part output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bin_runtime: Option<String>,
|
||||
/// The factory dependencies.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub factory_deps: Option<BTreeMap<String, String>>,
|
||||
/// The missing libraries.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub missing_libraries: Option<HashSet<String>>,
|
||||
}
|
||||
|
||||
impl Contract {
|
||||
///
|
||||
/// Returns the signature hash of the specified contract entry.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the hashes have not been requested in the `solc` call.
|
||||
///
|
||||
pub fn entry(&self, entry: &str) -> u32 {
|
||||
self.hashes
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.iter()
|
||||
.find_map(|(contract_entry, hash)| {
|
||||
if contract_entry.starts_with(entry) {
|
||||
Some(
|
||||
u32::from_str_radix(hash.as_str(), era_compiler_common::BASE_HEXADECIMAL)
|
||||
.expect("Test hash is always valid"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| panic!("Entry `{entry}` not found"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//!
|
||||
//! The `solc --combined-json` output.
|
||||
//!
|
||||
|
||||
pub mod contract;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::contract::Contract;
|
||||
|
||||
///
|
||||
/// The `solc --combined-json` output.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CombinedJson {
|
||||
/// The contract entries.
|
||||
pub contracts: BTreeMap<String, Contract>,
|
||||
/// The list of source files.
|
||||
#[serde(rename = "sourceList")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_list: Option<Vec<String>>,
|
||||
/// The source code extra data, including the AST.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sources: Option<serde_json::Value>,
|
||||
/// The `solc` compiler version.
|
||||
pub version: String,
|
||||
/// The `zksolc` compiler version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub zk_version: Option<String>,
|
||||
}
|
||||
|
||||
impl CombinedJson {
|
||||
///
|
||||
/// Returns the signature hash of the specified contract and entry.
|
||||
///
|
||||
pub fn entry(&self, path: &str, entry: &str) -> u32 {
|
||||
self.contracts
|
||||
.iter()
|
||||
.find_map(|(name, contract)| {
|
||||
if name.starts_with(path) {
|
||||
Some(contract)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.expect("Always exists")
|
||||
.entry(entry)
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the full contract path which can be found in `combined-json` output.
|
||||
///
|
||||
pub fn get_full_path(&self, name: &str) -> Option<String> {
|
||||
self.contracts.iter().find_map(|(path, _value)| {
|
||||
if let Some(last_slash_position) = path.rfind('/') {
|
||||
if let Some(colon_position) = path.rfind(':') {
|
||||
if &path[last_slash_position + 1..colon_position] == name {
|
||||
return Some(path.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Removes EVM artifacts to prevent their accidental usage.
|
||||
///
|
||||
pub fn remove_evm(&mut self) {
|
||||
for (_, contract) in self.contracts.iter_mut() {
|
||||
contract.bin = None;
|
||||
contract.bin_runtime = None;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Writes the JSON to the specified directory.
|
||||
///
|
||||
pub fn write_to_directory(
|
||||
self,
|
||||
output_directory: &Path,
|
||||
overwrite: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut file_path = output_directory.to_owned();
|
||||
file_path.push(format!("combined.{}", era_compiler_common::EXTENSION_JSON));
|
||||
|
||||
if file_path.exists() && !overwrite {
|
||||
eprintln!(
|
||||
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
File::create(&file_path)
|
||||
.map_err(|error| anyhow::anyhow!("File {:?} creating error: {}", file_path, error))?
|
||||
.write_all(serde_json::to_vec(&self).expect("Always valid").as_slice())
|
||||
.map_err(|error| anyhow::anyhow!("File {:?} writing error: {}", file_path, error))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
//!
|
||||
//! The Solidity compiler.
|
||||
//!
|
||||
|
||||
pub mod combined_json;
|
||||
pub mod pipeline;
|
||||
pub mod standard_json;
|
||||
pub mod version;
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use self::combined_json::CombinedJson;
|
||||
use self::pipeline::Pipeline;
|
||||
use self::standard_json::input::Input as StandardJsonInput;
|
||||
use self::standard_json::output::Output as StandardJsonOutput;
|
||||
use self::version::Version;
|
||||
|
||||
///
|
||||
/// The Solidity compiler.
|
||||
///
|
||||
pub struct Compiler {
|
||||
/// The binary executable name.
|
||||
pub executable: String,
|
||||
/// The lazily-initialized compiler version.
|
||||
pub version: Option<Version>,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
/// The default executable name.
|
||||
pub const DEFAULT_EXECUTABLE_NAME: &'static str = "solc";
|
||||
|
||||
/// The first version of `solc` with the support of standard JSON interface.
|
||||
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 4, 12);
|
||||
|
||||
/// The first version of `solc`, where Yul codegen is considered robust enough.
|
||||
pub const FIRST_YUL_VERSION: semver::Version = semver::Version::new(0, 8, 0);
|
||||
|
||||
/// The first version of `solc`, where `--via-ir` codegen mode is supported.
|
||||
pub const FIRST_VIA_IR_VERSION: semver::Version = semver::Version::new(0, 8, 13);
|
||||
|
||||
/// The last supported version of `solc`.
|
||||
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 24);
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
/// Different tools may use different `executable` names. For example, the integration tester
|
||||
/// uses `solc-<version>` format.
|
||||
///
|
||||
pub fn new(executable: String) -> anyhow::Result<Self> {
|
||||
if let Err(error) = which::which(executable.as_str()) {
|
||||
anyhow::bail!(
|
||||
"The `{executable}` executable not found in ${{PATH}}: {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
Ok(Self {
|
||||
executable,
|
||||
version: None,
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Compiles the Solidity `--standard-json` input into Yul IR.
|
||||
///
|
||||
pub fn standard_json(
|
||||
&mut self,
|
||||
mut input: StandardJsonInput,
|
||||
pipeline: Pipeline,
|
||||
base_path: Option<String>,
|
||||
include_paths: Vec<String>,
|
||||
allow_paths: Option<String>,
|
||||
) -> anyhow::Result<StandardJsonOutput> {
|
||||
let version = self.version()?;
|
||||
|
||||
let mut command = std::process::Command::new(self.executable.as_str());
|
||||
command.stdin(std::process::Stdio::piped());
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
command.arg("--standard-json");
|
||||
|
||||
if let Some(base_path) = base_path {
|
||||
command.arg("--base-path");
|
||||
command.arg(base_path);
|
||||
}
|
||||
for include_path in include_paths.into_iter() {
|
||||
command.arg("--include-path");
|
||||
command.arg(include_path);
|
||||
}
|
||||
if let Some(allow_paths) = allow_paths {
|
||||
command.arg("--allow-paths");
|
||||
command.arg(allow_paths);
|
||||
}
|
||||
|
||||
input.normalize(&version.default);
|
||||
|
||||
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
|
||||
|
||||
let input_json = serde_json::to_vec(&input).expect("Always valid");
|
||||
|
||||
let process = command.spawn().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess spawning error: {:?}", self.executable, error)
|
||||
})?;
|
||||
process
|
||||
.stdin
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("{} stdin getting error", self.executable))?
|
||||
.write_all(input_json.as_slice())
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!("{} stdin writing error: {:?}", self.executable, error)
|
||||
})?;
|
||||
|
||||
let output = process.wait_with_output().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess output error: {:?}", self.executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} error: {}",
|
||||
self.executable,
|
||||
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let mut output: StandardJsonOutput = era_compiler_common::deserialize_from_slice(
|
||||
output.stdout.as_slice(),
|
||||
)
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"{} subprocess output parsing error: {}\n{}",
|
||||
self.executable,
|
||||
error,
|
||||
era_compiler_common::deserialize_from_slice::<serde_json::Value>(
|
||||
output.stdout.as_slice()
|
||||
)
|
||||
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
|
||||
.unwrap_or_else(|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()),
|
||||
)
|
||||
})?;
|
||||
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?;
|
||||
output.remove_evm();
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
///
|
||||
/// The `solc --combined-json abi,hashes...` mirror.
|
||||
///
|
||||
pub fn combined_json(
|
||||
&self,
|
||||
paths: &[PathBuf],
|
||||
combined_json_argument: &str,
|
||||
) -> anyhow::Result<CombinedJson> {
|
||||
let mut command = std::process::Command::new(self.executable.as_str());
|
||||
command.args(paths);
|
||||
|
||||
let mut combined_json_flags = Vec::new();
|
||||
let mut combined_json_fake_flag_pushed = false;
|
||||
let mut filtered_flags = Vec::with_capacity(3);
|
||||
for flag in combined_json_argument.split(',') {
|
||||
match flag {
|
||||
flag @ "asm" | flag @ "bin" | flag @ "bin-runtime" => filtered_flags.push(flag),
|
||||
flag => combined_json_flags.push(flag),
|
||||
}
|
||||
}
|
||||
if combined_json_flags.is_empty() {
|
||||
combined_json_flags.push("ast");
|
||||
combined_json_fake_flag_pushed = true;
|
||||
}
|
||||
command.arg("--combined-json");
|
||||
command.arg(combined_json_flags.join(","));
|
||||
|
||||
let output = command.output().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
println!("{}", String::from_utf8_lossy(output.stdout.as_slice()));
|
||||
println!("{}", String::from_utf8_lossy(output.stderr.as_slice()));
|
||||
anyhow::bail!(
|
||||
"{} error: {}",
|
||||
self.executable,
|
||||
String::from_utf8_lossy(output.stdout.as_slice()).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let mut combined_json: CombinedJson = era_compiler_common::deserialize_from_slice(
|
||||
output.stdout.as_slice(),
|
||||
)
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"{} subprocess output parsing error: {}\n{}",
|
||||
self.executable,
|
||||
error,
|
||||
era_compiler_common::deserialize_from_slice::<serde_json::Value>(
|
||||
output.stdout.as_slice()
|
||||
)
|
||||
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
|
||||
.unwrap_or_else(|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()),
|
||||
)
|
||||
})?;
|
||||
for filtered_flag in filtered_flags.into_iter() {
|
||||
for (_path, contract) in combined_json.contracts.iter_mut() {
|
||||
match filtered_flag {
|
||||
"asm" => contract.asm = Some(serde_json::Value::Null),
|
||||
"bin" => contract.bin = Some("".to_owned()),
|
||||
"bin-runtime" => contract.bin_runtime = Some("".to_owned()),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
if combined_json_fake_flag_pushed {
|
||||
combined_json.source_list = None;
|
||||
combined_json.sources = None;
|
||||
}
|
||||
combined_json.remove_evm();
|
||||
|
||||
Ok(combined_json)
|
||||
}
|
||||
|
||||
///
|
||||
/// The `solc` Yul validator.
|
||||
///
|
||||
pub fn validate_yul(&self, path: &Path) -> anyhow::Result<()> {
|
||||
let mut command = std::process::Command::new(self.executable.as_str());
|
||||
command.arg("--strict-assembly");
|
||||
command.arg(path);
|
||||
|
||||
let output = command.output().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} error: {}",
|
||||
self.executable,
|
||||
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// The `solc --version` mini-parser.
|
||||
///
|
||||
pub fn version(&mut self) -> anyhow::Result<Version> {
|
||||
if let Some(version) = self.version.as_ref() {
|
||||
return Ok(version.to_owned());
|
||||
}
|
||||
|
||||
let mut command = std::process::Command::new(self.executable.as_str());
|
||||
command.arg("--version");
|
||||
let output = command.output().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} error: {}",
|
||||
self.executable,
|
||||
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(output.stdout.as_slice());
|
||||
let long = stdout
|
||||
.lines()
|
||||
.nth(1)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("{} version parsing: not enough lines", self.executable)
|
||||
})?
|
||||
.split(' ')
|
||||
.nth(1)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"{} version parsing: not enough words in the 2nd line",
|
||||
self.executable
|
||||
)
|
||||
})?
|
||||
.to_owned();
|
||||
let default: semver::Version = long
|
||||
.split('+')
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("{} version parsing: metadata dropping", self.executable)
|
||||
})?
|
||||
.parse()
|
||||
.map_err(|error| anyhow::anyhow!("{} version parsing: {}", self.executable, error))?;
|
||||
|
||||
let l2_revision: Option<semver::Version> = stdout
|
||||
.lines()
|
||||
.nth(2)
|
||||
.and_then(|line| line.split(' ').nth(1))
|
||||
.and_then(|line| line.split('-').nth(1))
|
||||
.and_then(|version| version.parse().ok());
|
||||
|
||||
let version = Version::new(long, default, l2_revision);
|
||||
if version.default < Self::FIRST_SUPPORTED_VERSION {
|
||||
anyhow::bail!(
|
||||
"`solc` versions <{} are not supported, found {}",
|
||||
Self::FIRST_SUPPORTED_VERSION,
|
||||
version.default
|
||||
);
|
||||
}
|
||||
if version.default > Self::LAST_SUPPORTED_VERSION {
|
||||
anyhow::bail!(
|
||||
"`solc` versions >{} are not supported, found {}",
|
||||
Self::LAST_SUPPORTED_VERSION,
|
||||
version.default
|
||||
);
|
||||
}
|
||||
|
||||
self.version = Some(version.clone());
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//!
|
||||
//! The Solidity compiler pipeline type.
|
||||
//!
|
||||
|
||||
use crate::solc::version::Version as SolcVersion;
|
||||
use crate::solc::Compiler as SolcCompiler;
|
||||
|
||||
///
|
||||
/// The Solidity compiler pipeline type.
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum Pipeline {
|
||||
/// The Yul IR.
|
||||
Yul,
|
||||
/// The EVM legacy assembly IR.
|
||||
EVMLA,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
///
|
||||
/// We always use EVMLA for Solidity <=0.7, or if the user does not want to compile via Yul.
|
||||
///
|
||||
pub fn new(solc_version: &SolcVersion, force_evmla: bool) -> Self {
|
||||
if solc_version.default < SolcCompiler::FIRST_YUL_VERSION || force_evmla {
|
||||
Self::EVMLA
|
||||
} else {
|
||||
Self::Yul
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//!
|
||||
//! The `solc --standard-json` input language.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` input language.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Language {
|
||||
/// The Solidity language.
|
||||
Solidity,
|
||||
/// The Yul IR.
|
||||
Yul,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Language {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Solidity => write!(f, "Solidity"),
|
||||
Self::Yul => write!(f, "Yul"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
//!
|
||||
//! The `solc --standard-json` input.
|
||||
//!
|
||||
|
||||
pub mod language;
|
||||
pub mod settings;
|
||||
pub mod source;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rayon::iter::IntoParallelIterator;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
use crate::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
|
||||
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
|
||||
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
|
||||
use crate::warning::Warning;
|
||||
|
||||
use self::language::Language;
|
||||
use self::settings::Settings;
|
||||
use self::source::Source;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` input.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Input {
|
||||
/// The input language.
|
||||
pub language: Language,
|
||||
/// The input source code files hashmap.
|
||||
pub sources: BTreeMap<String, Source>,
|
||||
/// The compiler settings.
|
||||
pub settings: Settings,
|
||||
/// The suppressed warnings.
|
||||
#[serde(skip_serializing)]
|
||||
pub suppressed_warnings: Option<Vec<Warning>>,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
///
|
||||
/// A shortcut constructor from stdin.
|
||||
///
|
||||
pub fn try_from_stdin(solc_pipeline: SolcPipeline) -> anyhow::Result<Self> {
|
||||
let mut input: Self = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?;
|
||||
input
|
||||
.settings
|
||||
.output_selection
|
||||
.get_or_insert_with(SolcStandardJsonInputSettingsSelection::default)
|
||||
.extend_with_required(solc_pipeline);
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor from paths.
|
||||
///
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn try_from_paths(
|
||||
language: Language,
|
||||
evm_version: Option<era_compiler_common::EVMVersion>,
|
||||
paths: &[PathBuf],
|
||||
library_map: Vec<String>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
output_selection: SolcStandardJsonInputSettingsSelection,
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer,
|
||||
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
|
||||
via_ir: bool,
|
||||
suppressed_warnings: Option<Vec<Warning>>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let sources = paths
|
||||
.into_par_iter()
|
||||
.map(|path| {
|
||||
let source = Source::try_from(path.as_path()).unwrap_or_else(|error| {
|
||||
panic!("Source code file {path:?} reading error: {error}")
|
||||
});
|
||||
(path.to_string_lossy().to_string(), source)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let libraries = Settings::parse_libraries(library_map)?;
|
||||
|
||||
Ok(Self {
|
||||
language,
|
||||
sources,
|
||||
settings: Settings::new(
|
||||
evm_version,
|
||||
libraries,
|
||||
remappings,
|
||||
output_selection,
|
||||
via_ir,
|
||||
optimizer,
|
||||
metadata,
|
||||
),
|
||||
suppressed_warnings,
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor from source code.
|
||||
///
|
||||
/// Only for the integration test purposes.
|
||||
///
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn try_from_sources(
|
||||
evm_version: Option<era_compiler_common::EVMVersion>,
|
||||
sources: BTreeMap<String, String>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
output_selection: SolcStandardJsonInputSettingsSelection,
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer,
|
||||
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
|
||||
via_ir: bool,
|
||||
suppressed_warnings: Option<Vec<Warning>>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let sources = sources
|
||||
.into_par_iter()
|
||||
.map(|(path, content)| (path, Source::from(content)))
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
language: Language::Solidity,
|
||||
sources,
|
||||
settings: Settings::new(
|
||||
evm_version,
|
||||
libraries,
|
||||
remappings,
|
||||
output_selection,
|
||||
via_ir,
|
||||
optimizer,
|
||||
metadata,
|
||||
),
|
||||
suppressed_warnings,
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the necessary defaults.
|
||||
///
|
||||
pub fn normalize(&mut self, version: &semver::Version) {
|
||||
self.settings.normalize(version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//!
|
||||
//! The `solc --standard-json` input settings metadata.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` input settings metadata.
|
||||
///
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Metadata {
|
||||
/// The bytecode hash mode.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bytecode_hash: Option<era_compiler_llvm_context::EraVMMetadataHash>,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(bytecode_hash: era_compiler_llvm_context::EraVMMetadataHash) -> Self {
|
||||
Self {
|
||||
bytecode_hash: Some(bytecode_hash),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
//!
|
||||
//! The `solc --standard-json` input settings.
|
||||
//!
|
||||
|
||||
pub mod metadata;
|
||||
pub mod optimizer;
|
||||
pub mod selection;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::metadata::Metadata;
|
||||
use self::optimizer::Optimizer;
|
||||
use self::selection::Selection;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` input settings.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Settings {
|
||||
/// The target EVM version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub evm_version: Option<era_compiler_common::EVMVersion>,
|
||||
/// The linker library addresses.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub libraries: Option<BTreeMap<String, BTreeMap<String, String>>>,
|
||||
/// The sorted list of remappings.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remappings: Option<BTreeSet<String>>,
|
||||
/// The output selection filters.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_selection: Option<Selection>,
|
||||
/// Whether to compile via IR. Only for testing with solc >=0.8.13.
|
||||
#[serde(
|
||||
rename = "viaIR",
|
||||
skip_serializing_if = "Option::is_none",
|
||||
skip_deserializing
|
||||
)]
|
||||
pub via_ir: Option<bool>,
|
||||
/// The optimizer settings.
|
||||
pub optimizer: Optimizer,
|
||||
/// The metadata settings.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<Metadata>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
evm_version: Option<era_compiler_common::EVMVersion>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
output_selection: Selection,
|
||||
via_ir: bool,
|
||||
optimizer: Optimizer,
|
||||
metadata: Option<Metadata>,
|
||||
) -> Self {
|
||||
Self {
|
||||
evm_version,
|
||||
libraries: Some(libraries),
|
||||
remappings,
|
||||
output_selection: Some(output_selection),
|
||||
via_ir: if via_ir { Some(true) } else { None },
|
||||
optimizer,
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the necessary defaults.
|
||||
///
|
||||
pub fn normalize(&mut self, version: &semver::Version) {
|
||||
self.optimizer.normalize(version);
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses the library list and returns their double hashmap with path and name as keys.
|
||||
///
|
||||
pub fn parse_libraries(
|
||||
input: Vec<String>,
|
||||
) -> anyhow::Result<BTreeMap<String, BTreeMap<String, String>>> {
|
||||
let mut libraries = BTreeMap::new();
|
||||
for (index, library) in input.into_iter().enumerate() {
|
||||
let mut path_and_address = library.split('=');
|
||||
let path = path_and_address
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("The library #{} path is missing", index))?;
|
||||
let mut file_and_contract = path.split(':');
|
||||
let file = file_and_contract
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("The library `{}` file name is missing", path))?;
|
||||
let contract = file_and_contract.next().ok_or_else(|| {
|
||||
anyhow::anyhow!("The library `{}` contract name is missing", path)
|
||||
})?;
|
||||
let address = path_and_address
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("The library `{}` address is missing", path))?;
|
||||
libraries
|
||||
.entry(file.to_owned())
|
||||
.or_insert_with(BTreeMap::new)
|
||||
.insert(contract.to_owned(), address.to_owned());
|
||||
}
|
||||
Ok(libraries)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//!
|
||||
//! The `solc --standard-json` input settings optimizer details.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` input settings optimizer details.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Details {
|
||||
/// Whether the pass is enabled.
|
||||
pub peephole: bool,
|
||||
/// Whether the pass is enabled.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub inliner: Option<bool>,
|
||||
/// Whether the pass is enabled.
|
||||
pub jumpdest_remover: bool,
|
||||
/// Whether the pass is enabled.
|
||||
pub order_literals: bool,
|
||||
/// Whether the pass is enabled.
|
||||
pub deduplicate: bool,
|
||||
/// Whether the pass is enabled.
|
||||
pub cse: bool,
|
||||
/// Whether the pass is enabled.
|
||||
pub constant_optimizer: bool,
|
||||
}
|
||||
|
||||
impl Details {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
peephole: bool,
|
||||
inliner: Option<bool>,
|
||||
jumpdest_remover: bool,
|
||||
order_literals: bool,
|
||||
deduplicate: bool,
|
||||
cse: bool,
|
||||
constant_optimizer: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
peephole,
|
||||
inliner,
|
||||
jumpdest_remover,
|
||||
order_literals,
|
||||
deduplicate,
|
||||
cse,
|
||||
constant_optimizer,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Creates a set of disabled optimizations.
|
||||
///
|
||||
pub fn disabled(version: &semver::Version) -> Self {
|
||||
let inliner = if version >= &semver::Version::new(0, 8, 5) {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self::new(false, inliner, false, false, false, false, false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//!
|
||||
//! The `solc --standard-json` input settings optimizer.
|
||||
//!
|
||||
|
||||
pub mod details;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::details::Details;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` input settings optimizer.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Optimizer {
|
||||
/// Whether the optimizer is enabled.
|
||||
pub enabled: bool,
|
||||
/// The optimization mode string.
|
||||
#[serde(skip_serializing)]
|
||||
pub mode: Option<char>,
|
||||
/// The `solc` optimizer details.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub details: Option<Details>,
|
||||
/// Whether to try to recompile with -Oz if the bytecode is too large.
|
||||
#[serde(skip_serializing)]
|
||||
pub fallback_to_optimizing_for_size: Option<bool>,
|
||||
/// Whether to disable the system request memoization.
|
||||
#[serde(skip_serializing)]
|
||||
pub disable_system_request_memoization: Option<bool>,
|
||||
}
|
||||
|
||||
impl Optimizer {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
enabled: bool,
|
||||
mode: Option<char>,
|
||||
version: &semver::Version,
|
||||
fallback_to_optimizing_for_size: bool,
|
||||
disable_system_request_memoization: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
mode,
|
||||
details: Some(Details::disabled(version)),
|
||||
fallback_to_optimizing_for_size: Some(fallback_to_optimizing_for_size),
|
||||
disable_system_request_memoization: Some(disable_system_request_memoization),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the necessary defaults.
|
||||
///
|
||||
pub fn normalize(&mut self, version: &semver::Version) {
|
||||
self.details = if version >= &semver::Version::new(0, 5, 5) {
|
||||
Some(Details::disabled(version))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Optimizer> for era_compiler_llvm_context::OptimizerSettings {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &Optimizer) -> Result<Self, Self::Error> {
|
||||
let mut result = match value.mode {
|
||||
Some(mode) => Self::try_from_cli(mode)?,
|
||||
None => Self::cycles(),
|
||||
};
|
||||
if value.fallback_to_optimizing_for_size.unwrap_or_default() {
|
||||
result.enable_fallback_to_size();
|
||||
}
|
||||
if value.disable_system_request_memoization.unwrap_or_default() {
|
||||
result.disable_system_request_memoization();
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//!
|
||||
//! The `solc --standard-json` expected output selection flag.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` expected output selection flag.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum Flag {
|
||||
/// The ABI JSON.
|
||||
#[serde(rename = "abi")]
|
||||
ABI,
|
||||
/// The metadata.
|
||||
#[serde(rename = "metadata")]
|
||||
Metadata,
|
||||
/// The developer documentation.
|
||||
#[serde(rename = "devdoc")]
|
||||
Devdoc,
|
||||
/// The user documentation.
|
||||
#[serde(rename = "userdoc")]
|
||||
Userdoc,
|
||||
/// The function signature hashes JSON.
|
||||
#[serde(rename = "evm.methodIdentifiers")]
|
||||
MethodIdentifiers,
|
||||
/// The storage layout.
|
||||
#[serde(rename = "storageLayout")]
|
||||
StorageLayout,
|
||||
/// The AST JSON.
|
||||
#[serde(rename = "ast")]
|
||||
AST,
|
||||
/// The Yul IR.
|
||||
#[serde(rename = "irOptimized")]
|
||||
Yul,
|
||||
/// The EVM legacy assembly JSON.
|
||||
#[serde(rename = "evm.legacyAssembly")]
|
||||
EVMLA,
|
||||
}
|
||||
|
||||
impl From<SolcPipeline> for Flag {
|
||||
fn from(pipeline: SolcPipeline) -> Self {
|
||||
match pipeline {
|
||||
SolcPipeline::Yul => Self::Yul,
|
||||
SolcPipeline::EVMLA => Self::EVMLA,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Flag {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ABI => write!(f, "abi"),
|
||||
Self::Metadata => write!(f, "metadata"),
|
||||
Self::Devdoc => write!(f, "devdoc"),
|
||||
Self::Userdoc => write!(f, "userdoc"),
|
||||
Self::MethodIdentifiers => write!(f, "evm.methodIdentifiers"),
|
||||
Self::StorageLayout => write!(f, "storageLayout"),
|
||||
Self::AST => write!(f, "ast"),
|
||||
Self::Yul => write!(f, "irOptimized"),
|
||||
Self::EVMLA => write!(f, "evm.legacyAssembly"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output file selection.
|
||||
//!
|
||||
|
||||
pub mod flag;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
use self::flag::Flag as SelectionFlag;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output file selection.
|
||||
///
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct File {
|
||||
/// The per-file output selections.
|
||||
#[serde(rename = "", skip_serializing_if = "Option::is_none")]
|
||||
pub per_file: Option<HashSet<SelectionFlag>>,
|
||||
/// The per-contract output selections.
|
||||
#[serde(rename = "*", skip_serializing_if = "Option::is_none")]
|
||||
pub per_contract: Option<HashSet<SelectionFlag>>,
|
||||
}
|
||||
|
||||
impl File {
|
||||
///
|
||||
/// Creates the selection required by our compilation process.
|
||||
///
|
||||
pub fn new_required(pipeline: SolcPipeline) -> Self {
|
||||
Self {
|
||||
per_file: Some(HashSet::from_iter([SelectionFlag::AST])),
|
||||
per_contract: Some(HashSet::from_iter([
|
||||
SelectionFlag::MethodIdentifiers,
|
||||
SelectionFlag::Metadata,
|
||||
SelectionFlag::from(pipeline),
|
||||
])),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Extends the user's output selection with flag required by our compilation process.
|
||||
///
|
||||
pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self {
|
||||
let required = Self::new_required(pipeline);
|
||||
|
||||
self.per_file
|
||||
.get_or_insert_with(HashSet::default)
|
||||
.extend(required.per_file.unwrap_or_default());
|
||||
self.per_contract
|
||||
.get_or_insert_with(HashSet::default)
|
||||
.extend(required.per_contract.unwrap_or_default());
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output selection.
|
||||
//!
|
||||
|
||||
pub mod file;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
use self::file::File as FileSelection;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output selection.
|
||||
///
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct Selection {
|
||||
/// Only the 'all' wildcard is available for robustness reasons.
|
||||
#[serde(rename = "*", skip_serializing_if = "Option::is_none")]
|
||||
pub all: Option<FileSelection>,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
///
|
||||
/// Creates the selection required by our compilation process.
|
||||
///
|
||||
pub fn new_required(pipeline: SolcPipeline) -> Self {
|
||||
Self {
|
||||
all: Some(FileSelection::new_required(pipeline)),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Extends the user's output selection with flag required by our compilation process.
|
||||
///
|
||||
pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self {
|
||||
self.all
|
||||
.get_or_insert_with(|| FileSelection::new_required(pipeline))
|
||||
.extend_with_required(pipeline);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//!
|
||||
//! The `solc --standard-json` input source.
|
||||
//!
|
||||
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` input source.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
/// The source code file content.
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl From<String> for Source {
|
||||
fn from(content: String) -> Self {
|
||||
Self { content }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for Source {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(path: &Path) -> Result<Self, Self::Error> {
|
||||
let content = if path.to_string_lossy() == "-" {
|
||||
let mut solidity_code = String::with_capacity(16384);
|
||||
std::io::stdin()
|
||||
.read_to_string(&mut solidity_code)
|
||||
.map_err(|error| anyhow::anyhow!("<stdin> reading error: {}", error))?;
|
||||
solidity_code
|
||||
} else {
|
||||
std::fs::read_to_string(path)
|
||||
.map_err(|error| anyhow::anyhow!("File {:?} reading error: {}", path, error))?
|
||||
};
|
||||
|
||||
Ok(Self { content })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//!
|
||||
//! The `solc <input>.sol --standard-json`.
|
||||
//!
|
||||
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
@@ -0,0 +1,25 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output contract EVM bytecode.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output contract EVM bytecode.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Bytecode {
|
||||
/// The bytecode object.
|
||||
pub object: String,
|
||||
}
|
||||
|
||||
impl Bytecode {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(object: String) -> Self {
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output contract EVM extra metadata.
|
||||
//!
|
||||
|
||||
pub mod recursive_function;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::recursive_function::RecursiveFunction;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output contract EVM extra metadata.
|
||||
///
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExtraMetadata {
|
||||
/// The list of recursive functions.
|
||||
#[serde(default = "Vec::new")]
|
||||
pub recursive_functions: Vec<RecursiveFunction>,
|
||||
}
|
||||
|
||||
impl ExtraMetadata {
|
||||
///
|
||||
/// Returns the recursive function reference for the specified tag.
|
||||
///
|
||||
pub fn get(
|
||||
&self,
|
||||
block_key: &era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
) -> Option<&RecursiveFunction> {
|
||||
for function in self.recursive_functions.iter() {
|
||||
match block_key.code_type {
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy => {
|
||||
if let Some(creation_tag) = function.creation_tag {
|
||||
if num::BigUint::from(creation_tag) == block_key.tag {
|
||||
return Some(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime => {
|
||||
if let Some(runtime_tag) = function.runtime_tag {
|
||||
if num::BigUint::from(runtime_tag) == block_key.tag {
|
||||
return Some(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output contract EVM recursive function.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output contract EVM recursive function.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RecursiveFunction {
|
||||
/// The function name.
|
||||
pub name: String,
|
||||
/// The creation code function block tag.
|
||||
pub creation_tag: Option<usize>,
|
||||
/// The runtime code function block tag.
|
||||
pub runtime_tag: Option<usize>,
|
||||
/// The number of input arguments.
|
||||
#[serde(rename = "totalParamSize")]
|
||||
pub input_size: usize,
|
||||
/// The number of output arguments.
|
||||
#[serde(rename = "totalRetParamSize")]
|
||||
pub output_size: usize,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output contract EVM data.
|
||||
//!
|
||||
|
||||
pub mod bytecode;
|
||||
pub mod extra_metadata;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::evmla::assembly::Assembly;
|
||||
|
||||
use self::bytecode::Bytecode;
|
||||
use self::extra_metadata::ExtraMetadata;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output contract EVM data.
|
||||
///
|
||||
/// It is replaced by EraVM data after compiling.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EVM {
|
||||
/// The contract EVM legacy assembly code.
|
||||
#[serde(rename = "legacyAssembly")]
|
||||
pub assembly: Option<Assembly>,
|
||||
/// The contract EraVM assembly code.
|
||||
#[serde(rename = "assembly")]
|
||||
pub assembly_text: Option<String>,
|
||||
/// The contract bytecode.
|
||||
/// Is reset by that of EraVM before yielding the compiled project artifacts.
|
||||
pub bytecode: Option<Bytecode>,
|
||||
/// The contract function signatures.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub method_identifiers: Option<BTreeMap<String, String>>,
|
||||
/// The extra EVMLA metadata.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub extra_metadata: Option<ExtraMetadata>,
|
||||
}
|
||||
|
||||
impl EVM {
|
||||
///
|
||||
/// Sets the EraVM assembly and bytecode.
|
||||
///
|
||||
pub fn modify(&mut self, assembly_text: String, bytecode: String) {
|
||||
self.assembly_text = Some(assembly_text);
|
||||
self.bytecode = Some(Bytecode::new(bytecode));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output contract.
|
||||
//!
|
||||
|
||||
pub mod evm;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::evm::EVM;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output contract.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Contract {
|
||||
/// The contract ABI.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub abi: Option<serde_json::Value>,
|
||||
/// The contract metadata.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
/// The contract developer documentation.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub devdoc: Option<serde_json::Value>,
|
||||
/// The contract user documentation.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub userdoc: Option<serde_json::Value>,
|
||||
/// The contract storage layout.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub storage_layout: Option<serde_json::Value>,
|
||||
/// Contract's bytecode and related objects
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub evm: Option<EVM>,
|
||||
/// The contract optimized IR code.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ir_optimized: Option<String>,
|
||||
/// The contract EraVM bytecode hash.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub hash: Option<String>,
|
||||
/// The contract factory dependencies.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub factory_dependencies: Option<BTreeMap<String, String>>,
|
||||
/// The contract missing libraries.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub missing_libraries: Option<HashSet<String>>,
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output error.
|
||||
//!
|
||||
|
||||
pub mod source_location;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::source_location::SourceLocation;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output error.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Error {
|
||||
/// The component type.
|
||||
pub component: String,
|
||||
/// The error code.
|
||||
pub error_code: Option<String>,
|
||||
/// The formatted error message.
|
||||
pub formatted_message: String,
|
||||
/// The non-formatted error message.
|
||||
pub message: String,
|
||||
/// The error severity.
|
||||
pub severity: String,
|
||||
/// The error location data.
|
||||
pub source_location: Option<SourceLocation>,
|
||||
/// The error type.
|
||||
pub r#type: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
///
|
||||
/// Returns the `ecrecover` function usage warning.
|
||||
///
|
||||
pub fn message_ecrecover(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Warning: It looks like you are using 'ecrecover' to validate a signature of a user account. │
|
||||
│ zkSync Era comes with native account abstraction support, therefore it is highly recommended NOT │
|
||||
│ to rely on the fact that the account has an ECDSA private key attached to it since accounts might│
|
||||
│ implement other signature schemes. │
|
||||
│ Read more about Account Abstraction at https://v2-docs.zksync.io/dev/developer-guides/aa.html │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the `<address payable>`'s `send` and `transfer` methods usage error.
|
||||
///
|
||||
pub fn message_send_and_transfer(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing │
|
||||
│ the gas amount. Such calls will fail depending on the pubdata costs. │
|
||||
│ This might be a false positive if you are using an interface (like IERC20) instead of the │
|
||||
│ native Solidity `send/transfer`. │
|
||||
│ Please use 'payable(<address>).call{value: <X>}("")' instead, but be careful with the reentrancy │
|
||||
│ attack. `send` and `transfer` send limited amount of gas that prevents reentrancy, whereas │
|
||||
│ `<address>.call{value: <X>}` sends all gas to the callee. Learn more on │
|
||||
│ https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the `extcodesize` instruction usage warning.
|
||||
///
|
||||
pub fn message_extcodesize(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is │
|
||||
│ usually needed in the following cases: │
|
||||
│ 1. To detect whether an address belongs to a smart contract. │
|
||||
│ 2. To detect whether the deploy code execution has finished. │
|
||||
│ zkSync Era comes with native account abstraction support (so accounts are smart contracts, │
|
||||
│ including private-key controlled EOAs), and you should avoid differentiating between contracts │
|
||||
│ and non-contract addresses. │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the `origin` instruction usage warning.
|
||||
///
|
||||
pub fn message_tx_origin(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Warning: You are checking for 'tx.origin' in your code, which might lead to unexpected behavior. │
|
||||
│ zkSync Era comes with native account abstraction support, and therefore the initiator of a │
|
||||
│ transaction might be different from the contract calling your code. It is highly recommended NOT │
|
||||
│ to rely on tx.origin, but use msg.sender instead. │
|
||||
│ Read more about Account Abstraction at https://v2-docs.zksync.io/dev/developer-guides/aa.html │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the internal function pointer usage error.
|
||||
///
|
||||
pub fn message_internal_function_pointer(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Error: Internal function pointers are not supported in EVM legacy assembly pipeline. │
|
||||
│ Please use the Yul IR codegen instead. │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "error".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Error".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Appends the contract path to the message..
|
||||
///
|
||||
pub fn push_contract_path(&mut self, path: &str) {
|
||||
self.formatted_message
|
||||
.push_str(format!("\n--> {path}\n").as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.formatted_message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output error source location.
|
||||
//!
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output error source location.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SourceLocation {
|
||||
/// The source file path.
|
||||
pub file: String,
|
||||
/// The start location.
|
||||
pub start: isize,
|
||||
/// The end location.
|
||||
pub end: isize,
|
||||
}
|
||||
|
||||
impl FromStr for SourceLocation {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
let mut parts = string.split(':');
|
||||
let start = parts
|
||||
.next()
|
||||
.map(|string| string.parse::<isize>())
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or_default();
|
||||
let length = parts
|
||||
.next()
|
||||
.map(|string| string.parse::<isize>())
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or_default();
|
||||
let file = parts.next().unwrap_or_default().to_owned();
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
start,
|
||||
end: start + length,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output.
|
||||
//!
|
||||
|
||||
pub mod contract;
|
||||
pub mod error;
|
||||
pub mod source;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sha3::Digest;
|
||||
|
||||
use crate::evmla::assembly::instruction::Instruction;
|
||||
use crate::evmla::assembly::Assembly;
|
||||
use crate::project::contract::ir::IR as ProjectContractIR;
|
||||
use crate::project::contract::Contract as ProjectContract;
|
||||
use crate::project::Project;
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
use crate::solc::version::Version as SolcVersion;
|
||||
use crate::warning::Warning;
|
||||
use crate::yul::lexer::Lexer;
|
||||
use crate::yul::parser::statement::object::Object;
|
||||
|
||||
use self::contract::Contract;
|
||||
use self::error::Error as SolcStandardJsonOutputError;
|
||||
use self::source::Source;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Output {
|
||||
/// The file-contract hashmap.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub contracts: Option<BTreeMap<String, BTreeMap<String, Contract>>>,
|
||||
/// The source code mapping data.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sources: Option<BTreeMap<String, Source>>,
|
||||
/// The compilation errors and warnings.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub errors: Option<Vec<SolcStandardJsonOutputError>>,
|
||||
/// The `solc` compiler version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
/// The `solc` compiler long version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub long_version: Option<String>,
|
||||
/// The `zksolc` compiler version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub zk_version: Option<String>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
///
|
||||
/// Converts the `solc` JSON output into a convenient project.
|
||||
///
|
||||
pub fn try_to_project(
|
||||
&mut self,
|
||||
source_code_files: BTreeMap<String, String>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
pipeline: SolcPipeline,
|
||||
solc_version: &SolcVersion,
|
||||
debug_config: Option<&era_compiler_llvm_context::DebugConfig>,
|
||||
) -> anyhow::Result<Project> {
|
||||
if let SolcPipeline::EVMLA = pipeline {
|
||||
self.preprocess_dependencies()?;
|
||||
}
|
||||
|
||||
let files = match self.contracts.as_ref() {
|
||||
Some(files) => files,
|
||||
None => {
|
||||
anyhow::bail!(
|
||||
"{}",
|
||||
self.errors
|
||||
.as_ref()
|
||||
.map(|errors| serde_json::to_string_pretty(errors).expect("Always valid"))
|
||||
.unwrap_or_else(|| "Unknown project assembling error".to_owned())
|
||||
);
|
||||
}
|
||||
};
|
||||
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 source = match pipeline {
|
||||
SolcPipeline::Yul => {
|
||||
let ir_optimized = match contract.ir_optimized.to_owned() {
|
||||
Some(ir_optimized) => ir_optimized,
|
||||
None => continue,
|
||||
};
|
||||
if ir_optimized.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(debug_config) = debug_config {
|
||||
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)
|
||||
})?;
|
||||
|
||||
ProjectContractIR::new_yul(ir_optimized.to_owned(), object)
|
||||
}
|
||||
SolcPipeline::EVMLA => {
|
||||
let evm = contract.evm.as_ref();
|
||||
let assembly = match evm.and_then(|evm| evm.assembly.to_owned()) {
|
||||
Some(assembly) => assembly.to_owned(),
|
||||
None => continue,
|
||||
};
|
||||
let extra_metadata = evm
|
||||
.and_then(|evm| evm.extra_metadata.to_owned())
|
||||
.unwrap_or_default();
|
||||
|
||||
ProjectContractIR::new_evmla(assembly, extra_metadata)
|
||||
}
|
||||
};
|
||||
|
||||
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 = ProjectContract::new(
|
||||
full_path.clone(),
|
||||
source_hash,
|
||||
solc_version.to_owned(),
|
||||
source,
|
||||
contract.metadata.to_owned(),
|
||||
);
|
||||
project_contracts.insert(full_path, project_contract);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Project::new(
|
||||
solc_version.to_owned(),
|
||||
project_contracts,
|
||||
libraries,
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Removes EVM artifacts to prevent their accidental usage.
|
||||
///
|
||||
pub fn remove_evm(&mut self) {
|
||||
if let Some(files) = self.contracts.as_mut() {
|
||||
for (_, file) in files.iter_mut() {
|
||||
for (_, contract) in file.iter_mut() {
|
||||
if let Some(evm) = contract.evm.as_mut() {
|
||||
evm.bytecode = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Traverses the AST and returns the list of additional errors and warnings.
|
||||
///
|
||||
pub fn preprocess_ast(
|
||||
&mut self,
|
||||
version: &SolcVersion,
|
||||
pipeline: SolcPipeline,
|
||||
suppressed_warnings: &[Warning],
|
||||
) -> anyhow::Result<()> {
|
||||
let sources = match self.sources.as_ref() {
|
||||
Some(sources) => sources,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let mut messages = Vec::new();
|
||||
for (path, source) in sources.iter() {
|
||||
if let Some(ast) = source.ast.as_ref() {
|
||||
let mut eravm_messages =
|
||||
Source::get_messages(ast, version, pipeline, suppressed_warnings);
|
||||
for message in eravm_messages.iter_mut() {
|
||||
message.push_contract_path(path.as_str());
|
||||
}
|
||||
messages.extend(eravm_messages);
|
||||
}
|
||||
}
|
||||
|
||||
self.errors = match self.errors.take() {
|
||||
Some(mut errors) => {
|
||||
errors.extend(messages);
|
||||
Some(errors)
|
||||
}
|
||||
None => Some(messages),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// The pass, which replaces with dependency indexes with actual data.
|
||||
///
|
||||
fn preprocess_dependencies(&mut self) -> anyhow::Result<()> {
|
||||
let files = match self.contracts.as_mut() {
|
||||
Some(files) => files,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let mut hash_path_mapping = BTreeMap::new();
|
||||
|
||||
for (path, contracts) in files.iter() {
|
||||
for (name, contract) in contracts.iter() {
|
||||
let full_path = format!("{path}:{name}");
|
||||
let hash = match contract
|
||||
.evm
|
||||
.as_ref()
|
||||
.and_then(|evm| evm.assembly.as_ref())
|
||||
.map(|assembly| assembly.keccak256())
|
||||
{
|
||||
Some(hash) => hash,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
hash_path_mapping.insert(hash, full_path);
|
||||
}
|
||||
}
|
||||
|
||||
for (path, contracts) in files.iter_mut() {
|
||||
for (name, contract) in contracts.iter_mut() {
|
||||
let assembly = match contract.evm.as_mut().and_then(|evm| evm.assembly.as_mut()) {
|
||||
Some(assembly) => assembly,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let full_path = format!("{path}:{name}");
|
||||
Self::preprocess_dependency_level(
|
||||
full_path.as_str(),
|
||||
assembly,
|
||||
&hash_path_mapping,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Preprocesses an assembly JSON structure dependency data map.
|
||||
///
|
||||
fn preprocess_dependency_level(
|
||||
full_path: &str,
|
||||
assembly: &mut Assembly,
|
||||
hash_path_mapping: &BTreeMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
assembly.set_full_path(full_path.to_owned());
|
||||
|
||||
let deploy_code_index_path_mapping =
|
||||
assembly.deploy_dependencies_pass(full_path, hash_path_mapping)?;
|
||||
if let Some(deploy_code_instructions) = assembly.code.as_deref_mut() {
|
||||
Instruction::replace_data_aliases(
|
||||
deploy_code_instructions,
|
||||
&deploy_code_index_path_mapping,
|
||||
)?;
|
||||
};
|
||||
|
||||
let runtime_code_index_path_mapping =
|
||||
assembly.runtime_dependencies_pass(full_path, hash_path_mapping)?;
|
||||
if let Some(runtime_code_instructions) = assembly
|
||||
.data
|
||||
.as_mut()
|
||||
.and_then(|data_map| data_map.get_mut("0"))
|
||||
.and_then(|data| data.get_assembly_mut())
|
||||
.and_then(|assembly| assembly.code.as_deref_mut())
|
||||
{
|
||||
Instruction::replace_data_aliases(
|
||||
runtime_code_instructions,
|
||||
&runtime_code_index_path_mapping,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
//!
|
||||
//! The `solc --standard-json` output source.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
use crate::solc::standard_json::output::error::Error as SolcStandardJsonOutputError;
|
||||
use crate::solc::version::Version as SolcVersion;
|
||||
use crate::warning::Warning;
|
||||
|
||||
///
|
||||
/// The `solc --standard-json` output source.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
/// The source code ID.
|
||||
pub id: usize,
|
||||
/// The source code AST.
|
||||
pub ast: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
///
|
||||
/// Checks the AST node for the `ecrecover` function usage.
|
||||
///
|
||||
pub fn check_ecrecover(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "FunctionCall" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let expression = ast.get("expression")?.as_object()?;
|
||||
if expression.get("nodeType")?.as_str()? != "Identifier" {
|
||||
return None;
|
||||
}
|
||||
if expression.get("name")?.as_str()? != "ecrecover" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_ecrecover(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks the AST node for the `<address payable>`'s `send` and `transfer` methods usage.
|
||||
///
|
||||
pub fn check_send_and_transfer(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "FunctionCall" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let expression = ast.get("expression")?.as_object()?;
|
||||
if expression.get("nodeType")?.as_str()? != "MemberAccess" {
|
||||
return None;
|
||||
}
|
||||
let member_name = expression.get("memberName")?.as_str()?;
|
||||
if member_name != "send" && member_name != "transfer" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_send_and_transfer(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks the AST node for the `extcodesize` assembly instruction usage.
|
||||
///
|
||||
pub fn check_assembly_extcodesize(
|
||||
ast: &serde_json::Value,
|
||||
) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "YulFunctionCall" {
|
||||
return None;
|
||||
}
|
||||
if ast
|
||||
.get("functionName")?
|
||||
.as_object()?
|
||||
.get("name")?
|
||||
.as_str()?
|
||||
!= "extcodesize"
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_extcodesize(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks the AST node for the `origin` assembly instruction usage.
|
||||
///
|
||||
pub fn check_assembly_origin(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "YulFunctionCall" {
|
||||
return None;
|
||||
}
|
||||
if ast
|
||||
.get("functionName")?
|
||||
.as_object()?
|
||||
.get("name")?
|
||||
.as_str()?
|
||||
!= "origin"
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_tx_origin(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks the AST node for the `tx.origin` value usage.
|
||||
///
|
||||
pub fn check_tx_origin(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "MemberAccess" {
|
||||
return None;
|
||||
}
|
||||
if ast.get("memberName")?.as_str()? != "origin" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let expression = ast.get("expression")?.as_object()?;
|
||||
if expression.get("nodeType")?.as_str()? != "Identifier" {
|
||||
return None;
|
||||
}
|
||||
if expression.get("name")?.as_str()? != "tx" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_tx_origin(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks the AST node for the internal function pointers value usage.
|
||||
///
|
||||
pub fn check_internal_function_pointer(
|
||||
ast: &serde_json::Value,
|
||||
) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "VariableDeclaration" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let type_descriptions = ast.get("typeDescriptions")?.as_object()?;
|
||||
if !type_descriptions
|
||||
.get("typeIdentifier")?
|
||||
.as_str()?
|
||||
.contains("function_internal")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
SolcStandardJsonOutputError::message_internal_function_pointer(
|
||||
ast.get("src")?.as_str(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the list of messages for some specific parts of the AST.
|
||||
///
|
||||
pub fn get_messages(
|
||||
ast: &serde_json::Value,
|
||||
version: &SolcVersion,
|
||||
pipeline: SolcPipeline,
|
||||
suppressed_warnings: &[Warning],
|
||||
) -> Vec<SolcStandardJsonOutputError> {
|
||||
let mut messages = Vec::new();
|
||||
if !suppressed_warnings.contains(&Warning::EcRecover) {
|
||||
if let Some(message) = Self::check_ecrecover(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
if !suppressed_warnings.contains(&Warning::SendTransfer) {
|
||||
if let Some(message) = Self::check_send_and_transfer(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
if !suppressed_warnings.contains(&Warning::ExtCodeSize) {
|
||||
if let Some(message) = Self::check_assembly_extcodesize(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
if !suppressed_warnings.contains(&Warning::TxOrigin) {
|
||||
if let Some(message) = Self::check_assembly_origin(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
if let Some(message) = Self::check_tx_origin(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
if SolcPipeline::EVMLA == pipeline && version.l2_revision.is_none() {
|
||||
if let Some(message) = Self::check_internal_function_pointer(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
match ast {
|
||||
serde_json::Value::Array(array) => {
|
||||
for element in array.iter() {
|
||||
messages.extend(Self::get_messages(
|
||||
element,
|
||||
version,
|
||||
pipeline,
|
||||
suppressed_warnings,
|
||||
));
|
||||
}
|
||||
}
|
||||
serde_json::Value::Object(object) => {
|
||||
for (_key, value) in object.iter() {
|
||||
messages.extend(Self::get_messages(
|
||||
value,
|
||||
version,
|
||||
pipeline,
|
||||
suppressed_warnings,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
messages
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the name of the last contract.
|
||||
///
|
||||
pub fn last_contract_name(&self) -> anyhow::Result<String> {
|
||||
self.ast
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("The AST is empty"))?
|
||||
.get("nodes")
|
||||
.and_then(|value| value.as_array())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("The last contract cannot be found in an empty list of nodes")
|
||||
})?
|
||||
.iter()
|
||||
.filter_map(
|
||||
|node| match node.get("nodeType").and_then(|node| node.as_str()) {
|
||||
Some("ContractDefinition") => Some(node.get("name")?.as_str()?.to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
.last()
|
||||
.ok_or_else(|| anyhow::anyhow!("The last contract not found in the AST"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//!
|
||||
//! The Solidity compiler version.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The Solidity compiler version.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Version {
|
||||
/// The long version string.
|
||||
pub long: String,
|
||||
/// The short `semver`.
|
||||
pub default: semver::Version,
|
||||
/// The L2 revision additional versioning.
|
||||
pub l2_revision: Option<semver::Version>,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
long: String,
|
||||
default: semver::Version,
|
||||
l2_revision: Option<semver::Version>,
|
||||
) -> Self {
|
||||
Self {
|
||||
long,
|
||||
default,
|
||||
l2_revision,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor for a simple version.
|
||||
///
|
||||
pub fn new_simple(version: semver::Version) -> Self {
|
||||
Self {
|
||||
long: version.to_string(),
|
||||
default: version,
|
||||
l2_revision: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
//! Common utility used for in frontend and integration tests.
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::project::Project;
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
|
||||
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
|
||||
use crate::solc::standard_json::input::Input as SolcStandardJsonInput;
|
||||
use crate::solc::standard_json::output::Output as SolcStandardJsonOutput;
|
||||
use crate::solc::Compiler as SolcCompiler;
|
||||
use crate::warning::Warning;
|
||||
|
||||
///
|
||||
/// Checks if the required executables are present in `${PATH}`.
|
||||
///
|
||||
fn check_dependencies() {
|
||||
for executable in [
|
||||
crate::r#const::DEFAULT_EXECUTABLE_NAME,
|
||||
SolcCompiler::DEFAULT_EXECUTABLE_NAME,
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
assert!(
|
||||
which::which(executable).is_ok(),
|
||||
"The `{executable}` executable not found in ${{PATH}}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Builds the Solidity project and returns the standard JSON output.
|
||||
///
|
||||
pub fn build_solidity(
|
||||
sources: BTreeMap<String, String>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
pipeline: SolcPipeline,
|
||||
optimizer_settings: era_compiler_llvm_context::OptimizerSettings,
|
||||
) -> anyhow::Result<SolcStandardJsonOutput> {
|
||||
check_dependencies();
|
||||
|
||||
inkwell::support::enable_llvm_pretty_stack_trace();
|
||||
era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM);
|
||||
let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME));
|
||||
|
||||
let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
|
||||
let solc_version = solc.version()?;
|
||||
|
||||
let input = SolcStandardJsonInput::try_from_sources(
|
||||
None,
|
||||
sources.clone(),
|
||||
libraries.clone(),
|
||||
remappings,
|
||||
SolcStandardJsonInputSettingsSelection::new_required(pipeline),
|
||||
SolcStandardJsonInputSettingsOptimizer::new(
|
||||
true,
|
||||
None,
|
||||
&solc_version.default,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
None,
|
||||
pipeline == SolcPipeline::Yul,
|
||||
None,
|
||||
)?;
|
||||
|
||||
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?;
|
||||
|
||||
let project = output.try_to_project(sources, libraries, pipeline, &solc_version, None)?;
|
||||
|
||||
let build: crate::Build = project.compile(optimizer_settings, false, false, false, None)?;
|
||||
build.write_to_standard_json(
|
||||
&mut output,
|
||||
&solc_version,
|
||||
&semver::Version::from_str(env!("CARGO_PKG_VERSION"))?,
|
||||
)?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
///
|
||||
/// Builds the Solidity project and returns the standard JSON output.
|
||||
///
|
||||
pub fn build_solidity_and_detect_missing_libraries(
|
||||
sources: BTreeMap<String, String>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
pipeline: SolcPipeline,
|
||||
) -> anyhow::Result<SolcStandardJsonOutput> {
|
||||
check_dependencies();
|
||||
|
||||
inkwell::support::enable_llvm_pretty_stack_trace();
|
||||
era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM);
|
||||
let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME));
|
||||
|
||||
let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
|
||||
let solc_version = solc.version()?;
|
||||
|
||||
let input = SolcStandardJsonInput::try_from_sources(
|
||||
None,
|
||||
sources.clone(),
|
||||
libraries.clone(),
|
||||
None,
|
||||
SolcStandardJsonInputSettingsSelection::new_required(pipeline),
|
||||
SolcStandardJsonInputSettingsOptimizer::new(
|
||||
true,
|
||||
None,
|
||||
&solc_version.default,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
None,
|
||||
pipeline == SolcPipeline::Yul,
|
||||
None,
|
||||
)?;
|
||||
|
||||
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?;
|
||||
|
||||
let project = output.try_to_project(sources, libraries, pipeline, &solc_version, None)?;
|
||||
|
||||
let missing_libraries = project.get_missing_libraries();
|
||||
missing_libraries.write_to_standard_json(
|
||||
&mut output,
|
||||
&solc.version()?,
|
||||
&semver::Version::from_str(env!("CARGO_PKG_VERSION"))?,
|
||||
)?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks if the Yul project can be built without errors.
|
||||
///
|
||||
pub fn build_yul(source_code: &str) -> anyhow::Result<()> {
|
||||
check_dependencies();
|
||||
|
||||
inkwell::support::enable_llvm_pretty_stack_trace();
|
||||
era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM);
|
||||
let optimizer_settings = era_compiler_llvm_context::OptimizerSettings::none();
|
||||
|
||||
let project =
|
||||
Project::try_from_yul_string(PathBuf::from("test.yul").as_path(), source_code, None)?;
|
||||
let _build = project.compile(optimizer_settings, false, false, false, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks if the built Solidity project contains the given warning.
|
||||
///
|
||||
pub fn check_solidity_warning(
|
||||
source_code: &str,
|
||||
warning_substring: &str,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
pipeline: SolcPipeline,
|
||||
skip_for_zkvm_edition: bool,
|
||||
suppressed_warnings: Option<Vec<Warning>>,
|
||||
) -> anyhow::Result<bool> {
|
||||
check_dependencies();
|
||||
|
||||
let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
|
||||
let solc_version = solc.version()?;
|
||||
if skip_for_zkvm_edition && solc_version.l2_revision.is_some() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_string(), source_code.to_string());
|
||||
let input = SolcStandardJsonInput::try_from_sources(
|
||||
None,
|
||||
sources.clone(),
|
||||
libraries,
|
||||
None,
|
||||
SolcStandardJsonInputSettingsSelection::new_required(pipeline),
|
||||
SolcStandardJsonInputSettingsOptimizer::new(
|
||||
true,
|
||||
None,
|
||||
&solc_version.default,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
None,
|
||||
pipeline == SolcPipeline::Yul,
|
||||
suppressed_warnings,
|
||||
)?;
|
||||
|
||||
let output = solc.standard_json(input, pipeline, None, vec![], None)?;
|
||||
let contains_warning = output
|
||||
.errors
|
||||
.ok_or_else(|| anyhow::anyhow!("Solidity compiler messages not found"))?
|
||||
.iter()
|
||||
.any(|error| error.formatted_message.contains(warning_substring));
|
||||
|
||||
Ok(contains_warning)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
};
|
||||
+3812
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "cli-tests",
|
||||
"version": "1.0.0",
|
||||
"title": "zksolc CLI Tests",
|
||||
"description": "Auto tests for verifying zksolc CLI",
|
||||
"repository": "https://github.com/matter-labs/era_compiler-solidity",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "npx jest --verbose --testPathPattern="
|
||||
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Matter Labs",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/shelljs": "^0.8.15",
|
||||
"jest": "^29.7.0",
|
||||
"shelljs": "^0.8.5",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.0;
|
||||
contract C {}
|
||||
@@ -0,0 +1,54 @@
|
||||
object "Test" {
|
||||
code {
|
||||
{
|
||||
mstore(64, 128)
|
||||
if callvalue() { revert(0, 0) }
|
||||
let _1 := datasize("Test_deployed")
|
||||
codecopy(0, dataoffset("Test_deployed"), _1)
|
||||
return(0, _1)
|
||||
}
|
||||
}
|
||||
object "Test_deployed" {
|
||||
code {
|
||||
{
|
||||
mstore(64, 128)
|
||||
if iszero(lt(calldatasize(), 4))
|
||||
{
|
||||
let _1 := 0
|
||||
switch shr(224, calldataload(_1))
|
||||
case 0x3df4ddf4 {
|
||||
if callvalue() { revert(_1, _1) }
|
||||
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
|
||||
let memPos := allocate_memory(_1)
|
||||
mstore(memPos, 0x2a)
|
||||
return(memPos, 32)
|
||||
}
|
||||
case 0x5a8ac02d {
|
||||
if callvalue() { revert(_1, _1) }
|
||||
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
|
||||
let memPos_1 := allocate_memory(_1)
|
||||
return(memPos_1, sub(abi_encode_uint256(memPos_1, 0x63), memPos_1))
|
||||
}
|
||||
}
|
||||
revert(0, 0)
|
||||
}
|
||||
function abi_encode_uint256(headStart, value0) -> tail
|
||||
{
|
||||
tail := add(headStart, 32)
|
||||
mstore(headStart, value0)
|
||||
}
|
||||
function allocate_memory(size) -> memPtr
|
||||
{
|
||||
memPtr := mload(64)
|
||||
let newFreePtr := add(memPtr, and(add(size, 31), not(31)))
|
||||
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))
|
||||
{
|
||||
mstore(0, shl(224, 0x4e487b71))
|
||||
mstore(4, 0x41)
|
||||
revert(0, 0x24)
|
||||
}
|
||||
mstore(64, newFreePtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
.text
|
||||
.file "main"
|
||||
.globl __entry
|
||||
__entry:
|
||||
.func_begin0:
|
||||
sub.s! 0, r2, r1
|
||||
jump.eq @.BB0_2
|
||||
add 32, r0, r1
|
||||
st.1 r0, r1
|
||||
st.1 r1, r0
|
||||
add @CPI0_1[0], r0, r1
|
||||
ret.ok.to_label r1, @DEFAULT_FAR_RETURN
|
||||
.BB0_2:
|
||||
add 42, r0, r1
|
||||
st.1 r0, r1
|
||||
add @CPI0_0[0], r0, r1
|
||||
ret.ok.to_label r1, @DEFAULT_FAR_RETURN
|
||||
.func_end0:
|
||||
|
||||
.note.GNU-stack
|
||||
.rodata
|
||||
CPI0_0:
|
||||
.cell 2535301200456458802993406410752
|
||||
CPI0_1:
|
||||
.cell 5070602400912917605986812821504
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as path from 'path';
|
||||
|
||||
const outputDir = 'artifacts';
|
||||
const binExtension = ':C.zbin';
|
||||
const asmExtension = ':C.zasm';
|
||||
const contractSolFilename = 'contract.sol';
|
||||
const contractYulFilename = 'contract.yul';
|
||||
const contractZkasmFilename = 'contract.zkasm';
|
||||
const pathToOutputDir = path.join( __dirname, '..', outputDir);
|
||||
const pathToContracts = path.join( __dirname, '..', 'src', 'contracts');
|
||||
const pathToBasicYulContract = path.join(pathToContracts, 'yul', contractYulFilename);
|
||||
const pathToBasicZkasmContract = path.join(pathToContracts, 'zkasm', contractZkasmFilename);
|
||||
const pathToBasicSolContract = path.join(pathToContracts, 'solidity', contractSolFilename);
|
||||
const pathToSolBinOutputFile = path.join(pathToOutputDir, contractSolFilename + binExtension);
|
||||
const pathToSolAsmOutputFile = path.join(pathToOutputDir, contractSolFilename + asmExtension);
|
||||
|
||||
export const paths = {
|
||||
outputDir: outputDir,
|
||||
binExtension: binExtension,
|
||||
asmExtension: asmExtension,
|
||||
contractSolFilename: contractSolFilename,
|
||||
contractZkasmFilename: contractZkasmFilename,
|
||||
contractYulFilename: contractYulFilename,
|
||||
pathToOutputDir: pathToOutputDir,
|
||||
pathToContracts: pathToContracts,
|
||||
pathToBasicZkasmContract: pathToBasicZkasmContract,
|
||||
pathToBasicSolContract: pathToBasicSolContract,
|
||||
pathToBasicYulContract: pathToBasicYulContract,
|
||||
pathToSolBinOutputFile: pathToSolBinOutputFile,
|
||||
pathToSolAsmOutputFile: pathToSolAsmOutputFile,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as shell from 'shelljs';
|
||||
import * as fs from 'fs';
|
||||
|
||||
interface CommandResult {
|
||||
output: string;
|
||||
exitCode: number;
|
||||
}
|
||||
|
||||
export const executeCommand = (command: string): CommandResult => {
|
||||
const result = shell.exec(command, {async: false});
|
||||
return {
|
||||
exitCode: result.code,
|
||||
output: result.stdout.trim() || result.stderr.trim(),
|
||||
};
|
||||
};
|
||||
|
||||
export const isFolderExist = (folder: string): boolean => {
|
||||
return shell.test('-d', folder);
|
||||
};
|
||||
|
||||
export const isFileExist = (pathToFileDir: string, fileName: string, fileExtension:string): boolean => {
|
||||
return shell.ls(pathToFileDir).stdout.includes(fileName + fileExtension);
|
||||
};
|
||||
|
||||
export const isFileEmpty = (file: string): boolean => {
|
||||
if (fs.existsSync(file)) {
|
||||
return (fs.readFileSync(file).length === 0);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import {executeCommand} from "../src/helper";
|
||||
import { paths } from '../src/entities';
|
||||
|
||||
|
||||
//id1746
|
||||
describe("Run with --asm by default", () => {
|
||||
const command = `zksolc ${paths.pathToBasicSolContract} --asm`;
|
||||
const result = executeCommand(command);
|
||||
const commandInvalid = 'zksolc --asm';
|
||||
const resultInvalid = executeCommand(commandInvalid);
|
||||
|
||||
it("Valid command exit code = 0", () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("--asm output is presented", () => {
|
||||
expect(result.output).toMatch(/(__entry:)/i);
|
||||
});
|
||||
|
||||
|
||||
it("solc exit code == zksolc exit code", () => {
|
||||
const command = `solc ${paths.pathToBasicSolContract} --asm`;
|
||||
const solcResult = executeCommand(command);
|
||||
expect(solcResult.exitCode).toBe(result.exitCode);
|
||||
});
|
||||
|
||||
it("run invalid: zksolc --asm", () => {
|
||||
expect(resultInvalid.output).toMatch(/(No input sources specified|Compilation aborted)/i);
|
||||
});
|
||||
it("Invalid command exit code = 1", () => {
|
||||
expect(resultInvalid.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it("Invalid solc exit code == Invalid zksolc exit code", () => {
|
||||
const command = 'solc --asm';
|
||||
const solcResult = executeCommand(command);
|
||||
expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import {executeCommand, isFolderExist, isFileExist, isFileEmpty} from "../src/helper";
|
||||
import { paths } from '../src/entities';
|
||||
|
||||
|
||||
//id1762
|
||||
describe("Run zksolc without any options", () => {
|
||||
const command = 'zksolc';
|
||||
const result = executeCommand(command);
|
||||
|
||||
it("Info with help is presented", () => {
|
||||
expect(result.output).toMatch(/(No input sources specified|Error(s) found.)/i);
|
||||
});
|
||||
|
||||
it("Exit code = 1", () => {
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it("solc exit code == zksolc exit code", () => {
|
||||
const command = 'solc';
|
||||
const solcResult = executeCommand(command);
|
||||
expect(solcResult.exitCode).toBe(result.exitCode);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//#1713
|
||||
describe("Default run a command from the help", () => {
|
||||
|
||||
const command = `zksolc ${paths.pathToBasicSolContract} -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on zksolc with full path on Windows cmd
|
||||
const result = executeCommand(command);
|
||||
|
||||
it("Compiler run successful", () => {
|
||||
expect(result.output).toMatch(/(Compiler run successful.)/i);
|
||||
});
|
||||
it("Exit code = 0", () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
it("Output dir is created", () => {
|
||||
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
|
||||
});
|
||||
xit("Output file is created", () => { // a bug on windows
|
||||
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true);
|
||||
});
|
||||
it("the output file is not empty", () => {
|
||||
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
|
||||
});
|
||||
it("No 'Error'/'Warning'/'Fail' in the output", () => {
|
||||
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
|
||||
});
|
||||
});
|
||||
|
||||
//#1818
|
||||
describe("Default run a command from the help", () => {
|
||||
|
||||
const command = `zksolc ${paths.pathToBasicSolContract} -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on zksolc with full path on Windows cmd
|
||||
const result = executeCommand(command);
|
||||
|
||||
it("Compiler run successful", () => {
|
||||
expect(result.output).toMatch(/(Compiler run successful.)/i);
|
||||
});
|
||||
it("Exit code = 0", () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
it("Output dir is created", () => {
|
||||
expect(isFolderExist(paths.pathToOutputDir)).toBe(true);
|
||||
});
|
||||
xit("Output files are created", () => { // a bug on windows
|
||||
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.binExtension)).toBe(true);
|
||||
expect(isFileExist(paths.pathToOutputDir, paths.contractSolFilename, paths.asmExtension)).toBe(true);
|
||||
});
|
||||
it("the output files are not empty", () => {
|
||||
expect(isFileEmpty(paths.pathToSolBinOutputFile)).toBe(false);
|
||||
expect(isFileEmpty(paths.pathToSolAsmOutputFile)).toBe(false);
|
||||
});
|
||||
it("No 'Error'/'Warning'/'Fail' in the output", () => {
|
||||
expect(result.output).not.toMatch(/([Ee]rror|[Ww]arning|[Ff]ail)/i);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import {executeCommand} from "../src/helper";
|
||||
import { paths } from '../src/entities';
|
||||
|
||||
|
||||
//id1743
|
||||
describe("Run with --yul by default", () => {
|
||||
const command = `zksolc ${paths.pathToBasicYulContract} --yul`;
|
||||
const result = executeCommand(command);
|
||||
const commandInvalid = 'zksolc --yul';
|
||||
const resultInvalid = executeCommand(commandInvalid);
|
||||
|
||||
it("Valid command exit code = 0", () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("--yul output is presented", () => {
|
||||
expect(result.output).toMatch(/(Compiler run successful)/i);
|
||||
expect(result.output).toMatch(/(No output requested)/i);
|
||||
});
|
||||
|
||||
|
||||
xit("solc exit code == zksolc exit code", () => { // unknown solc issue for datatype of the contract
|
||||
const command = `solc ${paths.pathToBasicSolContract} --yul`;
|
||||
const solcResult = executeCommand(command);
|
||||
expect(solcResult.exitCode).toBe(result.exitCode);
|
||||
});
|
||||
|
||||
it("run invalid: zksolc --yul", () => {
|
||||
expect(resultInvalid.output).toMatch(/(The input file is missing)/i);
|
||||
});
|
||||
it("Invalid command exit code = 1", () => {
|
||||
expect(resultInvalid.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it("Invalid solc exit code == Invalid zksolc exit code", () => {
|
||||
const command = 'solc --yul';
|
||||
const solcResult = executeCommand(command);
|
||||
expect(solcResult.exitCode).toBe(resultInvalid.exitCode);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import {executeCommand} from "../src/helper";
|
||||
import { paths } from '../src/entities';
|
||||
|
||||
|
||||
//id1745
|
||||
describe("Run with --zkasm by default", () => {
|
||||
const command = `zksolc ${paths.pathToBasicZkasmContract} --zkasm`;
|
||||
const result = executeCommand(command);
|
||||
|
||||
it("Valid command exit code = 0", () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("--zkasm output is presented", () => {
|
||||
expect(result.output).toMatch(/(Compiler run successful)/i);
|
||||
expect(result.output).toMatch(/(No output requested)/i);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"module": "CommonJS",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests for factory dependencies.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
pub const MAIN_CODE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity >=0.4.16;
|
||||
|
||||
import "./callable.sol";
|
||||
|
||||
contract Main {
|
||||
function main() external returns(uint256) {
|
||||
Callable callable = new Callable();
|
||||
|
||||
callable.set(10);
|
||||
return callable.get();
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const CALLABLE_CODE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity >=0.4.16;
|
||||
|
||||
contract Callable {
|
||||
uint256 value;
|
||||
|
||||
function set(uint256 x) external {
|
||||
value = x;
|
||||
}
|
||||
|
||||
function get() external view returns(uint256) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("main.sol".to_owned(), MAIN_CODE.to_owned());
|
||||
sources.insert("callable.sol".to_owned(), CALLABLE_CODE.to_owned());
|
||||
|
||||
let output = super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Build failure");
|
||||
|
||||
assert_eq!(
|
||||
output
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Missing field `contracts`")
|
||||
.get("main.sol")
|
||||
.expect("Missing file `main.sol`")
|
||||
.get("Main")
|
||||
.expect("Missing contract `main.sol:Main`")
|
||||
.factory_dependencies
|
||||
.as_ref()
|
||||
.expect("Missing field `factory_dependencies`")
|
||||
.len(),
|
||||
1,
|
||||
"Expected 1 factory dependency in `main.sol:Main`"
|
||||
);
|
||||
assert_eq!(
|
||||
output
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Missing field `contracts`")
|
||||
.get("callable.sol")
|
||||
.expect("Missing file `callable.sol`")
|
||||
.get("Callable")
|
||||
.expect("Missing contract `callable.sol:Callable`")
|
||||
.factory_dependencies
|
||||
.as_ref()
|
||||
.expect("Missing field `factory_dependencies`")
|
||||
.len(),
|
||||
0,
|
||||
"Expected 0 factory dependencies in `callable.sol:Callable`"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests for IR artifacts.
|
||||
//!
|
||||
//! The tests check if the IR artifacts are kept in the final output.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
#[test]
|
||||
fn yul() {
|
||||
let source_code = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract Test {
|
||||
function main() public view returns (uint) {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), source_code.to_owned());
|
||||
|
||||
let build = super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
|
||||
assert!(
|
||||
build
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.get("test.sol")
|
||||
.expect("Always exists")
|
||||
.get("Test")
|
||||
.expect("Always exists")
|
||||
.ir_optimized
|
||||
.is_some(),
|
||||
"Yul IR is missing"
|
||||
);
|
||||
assert!(
|
||||
build
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.get("test.sol")
|
||||
.expect("Always exists")
|
||||
.get("Test")
|
||||
.expect("Always exists")
|
||||
.evm
|
||||
.as_ref()
|
||||
.expect("EVM object is missing")
|
||||
.assembly
|
||||
.is_none(),
|
||||
"EVMLA IR is present although not requested"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evmla() {
|
||||
let source_code = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract Test {
|
||||
function main() public view returns (uint) {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), source_code.to_owned());
|
||||
|
||||
let build = super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::EVMLA,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
assert!(
|
||||
build
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.get("test.sol")
|
||||
.expect("Always exists")
|
||||
.get("Test")
|
||||
.expect("Always exists")
|
||||
.evm
|
||||
.as_ref()
|
||||
.expect("EVM object is missing")
|
||||
.assembly
|
||||
.is_some(),
|
||||
"EVMLA IR is missing",
|
||||
);
|
||||
assert!(
|
||||
build
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.get("test.sol")
|
||||
.expect("Always exists")
|
||||
.get("Test")
|
||||
.expect("Always exists")
|
||||
.ir_optimized
|
||||
.is_none(),
|
||||
"Yul IR is present although not requested",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests for libraries.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
pub const LIBRARY_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
// A simple library with at least one external method
|
||||
library SimpleLibrary {
|
||||
function add(uint256 a, uint256 b) external pure returns (uint256) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
// A contract calling that library
|
||||
contract SimpleContract {
|
||||
using SimpleLibrary for uint256;
|
||||
|
||||
function performAlgorithm(uint256 a, uint256 b) public pure returns (uint256) {
|
||||
uint sum = 0;
|
||||
if (a > b) {
|
||||
while (true) {
|
||||
sum += a.add(b);
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn not_specified() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned());
|
||||
|
||||
for pipeline in [SolcPipeline::EVMLA, SolcPipeline::Yul] {
|
||||
let output = super::build_solidity_and_detect_missing_libraries(
|
||||
sources.clone(),
|
||||
BTreeMap::new(),
|
||||
pipeline,
|
||||
)
|
||||
.expect("Test failure");
|
||||
assert!(
|
||||
output
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.get("test.sol")
|
||||
.expect("Always exists")
|
||||
.get("SimpleContract")
|
||||
.expect("Always exists")
|
||||
.missing_libraries
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.contains("test.sol:SimpleLibrary"),
|
||||
"Missing library not detected"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn specified() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned());
|
||||
|
||||
let mut libraries = BTreeMap::new();
|
||||
libraries
|
||||
.entry("test.sol".to_string())
|
||||
.or_insert_with(BTreeMap::new)
|
||||
.entry("SimpleLibrary".to_string())
|
||||
.or_insert("0x00000000000000000000000000000000DEADBEEF".to_string());
|
||||
|
||||
for pipeline in [SolcPipeline::EVMLA, SolcPipeline::Yul] {
|
||||
let output = super::build_solidity_and_detect_missing_libraries(
|
||||
sources.clone(),
|
||||
libraries.clone(),
|
||||
pipeline,
|
||||
)
|
||||
.expect("Test failure");
|
||||
assert!(
|
||||
output
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.get("test.sol")
|
||||
.expect("Always exists")
|
||||
.get("SimpleContract")
|
||||
.expect("Always exists")
|
||||
.missing_libraries
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.is_empty(),
|
||||
"The list of missing libraries must be empty"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests for messages.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
use crate::warning::Warning;
|
||||
|
||||
pub const ECRECOVER_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract ECRecoverExample {
|
||||
function recoverAddress(
|
||||
bytes32 messageHash,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) public pure returns (address) {
|
||||
return ecrecover(messageHash, v, r, s);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn ecrecover() {
|
||||
assert!(
|
||||
super::check_solidity_warning(
|
||||
ECRECOVER_TEST_SOURCE,
|
||||
"Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
None,
|
||||
).expect("Test failure")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ecrecover_suppressed() {
|
||||
assert!(
|
||||
!super::check_solidity_warning(
|
||||
ECRECOVER_TEST_SOURCE,
|
||||
"Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
Some(vec![Warning::EcRecover]),
|
||||
).expect("Test failure")
|
||||
);
|
||||
}
|
||||
|
||||
pub const SEND_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract SendExample {
|
||||
address payable public recipient;
|
||||
|
||||
constructor(address payable _recipient) {
|
||||
recipient = _recipient;
|
||||
}
|
||||
|
||||
function forwardEther() external payable {
|
||||
bool success = recipient.send(msg.value);
|
||||
require(success, "Failed to send Ether");
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn send() {
|
||||
assert!(
|
||||
super::check_solidity_warning(
|
||||
SEND_TEST_SOURCE,
|
||||
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
None,
|
||||
).expect("Test failure")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_suppressed() {
|
||||
assert!(
|
||||
!super::check_solidity_warning(
|
||||
SEND_TEST_SOURCE,
|
||||
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
Some(vec![Warning::SendTransfer]),
|
||||
).expect("Test failure")
|
||||
);
|
||||
}
|
||||
|
||||
pub const TRANSFER_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract TransferExample {
|
||||
address payable public recipient;
|
||||
|
||||
constructor(address payable _recipient) {
|
||||
recipient = _recipient;
|
||||
}
|
||||
|
||||
function forwardEther() external payable {
|
||||
recipient.transfer(msg.value);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn transfer() {
|
||||
assert!(
|
||||
super::check_solidity_warning(
|
||||
TRANSFER_TEST_SOURCE,
|
||||
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
None,
|
||||
).expect("Test failure")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_suppressed() {
|
||||
assert!(
|
||||
!super::check_solidity_warning(
|
||||
TRANSFER_TEST_SOURCE,
|
||||
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
Some(vec![Warning::SendTransfer]),
|
||||
).expect("Test failure")
|
||||
);
|
||||
}
|
||||
|
||||
pub const EXTCODESIZE_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract ExternalCodeSize {
|
||||
function getExternalCodeSize(address target) public view returns (uint256) {
|
||||
uint256 codeSize;
|
||||
assembly {
|
||||
codeSize := extcodesize(target)
|
||||
}
|
||||
return codeSize;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn extcodesize() {
|
||||
assert!(super::check_solidity_warning(
|
||||
EXTCODESIZE_TEST_SOURCE,
|
||||
"Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extcodesize_suppressed() {
|
||||
assert!(!super::check_solidity_warning(
|
||||
EXTCODESIZE_TEST_SOURCE,
|
||||
"Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
Some(vec![Warning::ExtCodeSize]),
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
|
||||
pub const TX_ORIGIN_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract TxOriginExample {
|
||||
function isOriginSender() public view returns (bool) {
|
||||
return tx.origin == msg.sender;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn tx_origin() {
|
||||
assert!(super::check_solidity_warning(
|
||||
TX_ORIGIN_TEST_SOURCE,
|
||||
"Warning: You are checking for 'tx.origin' in your code, which might lead to",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_origin_suppressed() {
|
||||
assert!(!super::check_solidity_warning(
|
||||
TX_ORIGIN_TEST_SOURCE,
|
||||
"Warning: You are checking for 'tx.origin' in your code, which might lead to",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
Some(vec![Warning::TxOrigin]),
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
|
||||
pub const TX_ORIGIN_ASSEMBLY_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract TxOriginExample {
|
||||
function isOriginSender() public view returns (bool) {
|
||||
address txOrigin;
|
||||
address sender = msg.sender;
|
||||
|
||||
assembly {
|
||||
txOrigin := origin() // Get the transaction origin using the 'origin' instruction
|
||||
}
|
||||
|
||||
return txOrigin == sender;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn tx_origin_assembly() {
|
||||
assert!(super::check_solidity_warning(
|
||||
TX_ORIGIN_ASSEMBLY_TEST_SOURCE,
|
||||
"Warning: You are checking for 'tx.origin' in your code, which might lead to",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_origin_assembly_suppressed() {
|
||||
assert!(!super::check_solidity_warning(
|
||||
TX_ORIGIN_ASSEMBLY_TEST_SOURCE,
|
||||
"Warning: You are checking for 'tx.origin' in your code, which might lead to",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::Yul,
|
||||
false,
|
||||
Some(vec![Warning::TxOrigin]),
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_function_pointer_argument() {
|
||||
let source_code = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract InternalFunctionPointerExample {
|
||||
function add(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
function executeOperation(
|
||||
function (uint256, uint256) internal pure returns (uint256) operation,
|
||||
uint256 a,
|
||||
uint256 b
|
||||
) private pure returns (uint256) {
|
||||
return operation(a, b);
|
||||
}
|
||||
|
||||
function testAdd(uint256 a, uint256 b) public pure returns (uint256) {
|
||||
return executeOperation(add, a, b);
|
||||
}
|
||||
|
||||
function testSub(uint256 a, uint256 b) public pure returns (uint256) {
|
||||
return executeOperation(sub, a, b);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
assert!(super::check_solidity_warning(
|
||||
source_code,
|
||||
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::EVMLA,
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_function_pointer_stack() {
|
||||
let source_code = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract StackFunctionPointerExample {
|
||||
function add(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
function testAdd(uint256 a, uint256 b) public pure returns (uint256) {
|
||||
function (uint256, uint256) internal pure returns (uint256) operation = add;
|
||||
return operation(a, b);
|
||||
}
|
||||
|
||||
function testSub(uint256 a, uint256 b) public pure returns (uint256) {
|
||||
function (uint256, uint256) internal pure returns (uint256) operation = sub;
|
||||
return operation(a, b);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
assert!(super::check_solidity_warning(
|
||||
source_code,
|
||||
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::EVMLA,
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_function_pointer_storage() {
|
||||
let source_code = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract StorageFunctionPointerExample {
|
||||
function add(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
function (uint256, uint256) internal pure returns (uint256) operation;
|
||||
bool private isOperationSet = false;
|
||||
|
||||
function setOperation(bool isAdd) public {
|
||||
if (isAdd) {
|
||||
operation = add;
|
||||
} else {
|
||||
operation = sub;
|
||||
}
|
||||
isOperationSet = true;
|
||||
}
|
||||
|
||||
function executeOperation(uint256 a, uint256 b) public view returns (uint256) {
|
||||
require(isOperationSet, "Operation not set");
|
||||
return operation(a, b);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
assert!(super::check_solidity_warning(
|
||||
source_code,
|
||||
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
|
||||
BTreeMap::new(),
|
||||
SolcPipeline::EVMLA,
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.expect("Test failure"));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
mod factory_dependency;
|
||||
mod ir_artifacts;
|
||||
mod libraries;
|
||||
mod messages;
|
||||
mod optimizer;
|
||||
mod remappings;
|
||||
mod runtime_code;
|
||||
mod unsupported_opcodes;
|
||||
|
||||
pub(crate) use super::test_utils::*;
|
||||
@@ -0,0 +1,137 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests for the optimizer.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
pub const SOURCE_CODE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity >=0.5.0;
|
||||
|
||||
contract Test {
|
||||
uint8 constant ARRAY_SIZE = 40;
|
||||
uint128 constant P = 257;
|
||||
uint128 constant MODULO = 1000000007;
|
||||
|
||||
function complex() public pure returns(uint64) {
|
||||
uint8[ARRAY_SIZE] memory array;
|
||||
// generate array where first half equals second
|
||||
for(uint8 i = 0; i < ARRAY_SIZE; i++) {
|
||||
array[i] = (i % (ARRAY_SIZE / 2)) * (255 / (ARRAY_SIZE / 2 - 1));
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
for(uint8 i = 0; i < ARRAY_SIZE/2; i++) {
|
||||
result = result && hash(array, 0, i + 1) == hash(array, ARRAY_SIZE/2, ARRAY_SIZE/2 + i + 1)
|
||||
&& hash(array, i, ARRAY_SIZE/2) == hash(array, i + ARRAY_SIZE/2, ARRAY_SIZE);
|
||||
}
|
||||
if (result) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function hash(uint8[ARRAY_SIZE] memory array, uint8 begin, uint8 end) private pure returns(uint128) {
|
||||
uint128 h = 0;
|
||||
for(uint8 i = begin; i < end; i++) {
|
||||
h = (h * P + array[i]) % MODULO;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn optimizer() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), SOURCE_CODE.to_owned());
|
||||
|
||||
let build_unoptimized = super::build_solidity(
|
||||
sources.clone(),
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::none(),
|
||||
)
|
||||
.expect("Build failure");
|
||||
let build_optimized_for_cycles = super::build_solidity(
|
||||
sources.clone(),
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Build failure");
|
||||
let build_optimized_for_size = super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::size(),
|
||||
)
|
||||
.expect("Build failure");
|
||||
|
||||
let size_when_unoptimized = build_unoptimized
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Missing field `contracts`")
|
||||
.get("test.sol")
|
||||
.expect("Missing file `test.sol`")
|
||||
.get("Test")
|
||||
.expect("Missing contract `test.sol:Test`")
|
||||
.evm
|
||||
.as_ref()
|
||||
.expect("Missing EVM data")
|
||||
.bytecode
|
||||
.as_ref()
|
||||
.expect("Missing bytecode")
|
||||
.object
|
||||
.len();
|
||||
let size_when_optimized_for_cycles = build_optimized_for_cycles
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Missing field `contracts`")
|
||||
.get("test.sol")
|
||||
.expect("Missing file `test.sol`")
|
||||
.get("Test")
|
||||
.expect("Missing contract `test.sol:Test`")
|
||||
.evm
|
||||
.as_ref()
|
||||
.expect("Missing EVM data")
|
||||
.bytecode
|
||||
.as_ref()
|
||||
.expect("Missing bytecode")
|
||||
.object
|
||||
.len();
|
||||
let size_when_optimized_for_size = build_optimized_for_size
|
||||
.contracts
|
||||
.as_ref()
|
||||
.expect("Missing field `contracts`")
|
||||
.get("test.sol")
|
||||
.expect("Missing file `test.sol`")
|
||||
.get("Test")
|
||||
.expect("Missing contract `test.sol:Test`")
|
||||
.evm
|
||||
.as_ref()
|
||||
.expect("Missing EVM data")
|
||||
.bytecode
|
||||
.as_ref()
|
||||
.expect("Missing bytecode")
|
||||
.object
|
||||
.len();
|
||||
|
||||
assert!(
|
||||
size_when_optimized_for_cycles < size_when_unoptimized,
|
||||
"Expected the cycles-optimized bytecode to be smaller than the unoptimized. Optimized: {}B, Unoptimized: {}B", size_when_optimized_for_cycles, size_when_unoptimized,
|
||||
);
|
||||
assert!(
|
||||
size_when_optimized_for_size < size_when_unoptimized,
|
||||
"Expected the size-optimized bytecode to be smaller than the unoptimized. Optimized: {}B, Unoptimized: {}B", size_when_optimized_for_size, size_when_unoptimized,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests for remappings.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
pub const CALLEE_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity >=0.4.16;
|
||||
|
||||
contract Callable {
|
||||
function f(uint a) public pure returns(uint) {
|
||||
return a * 2;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const CALLER_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity >=0.4.16;
|
||||
|
||||
import "libraries/default/callable.sol";
|
||||
|
||||
contract Main {
|
||||
function main(Callable callable) public returns(uint) {
|
||||
return callable.f(5);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("./test.sol".to_owned(), CALLER_TEST_SOURCE.to_owned());
|
||||
sources.insert("./callable.sol".to_owned(), CALLEE_TEST_SOURCE.to_owned());
|
||||
|
||||
let mut remappings = BTreeSet::new();
|
||||
remappings.insert("libraries/default/=./".to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
Some(remappings),
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests for runtime code.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "runtimeCode is not supported")]
|
||||
fn default() {
|
||||
let source_code = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract A {}
|
||||
|
||||
contract Test {
|
||||
function main() public pure returns(bytes memory) {
|
||||
return type(A).runtimeCode;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), source_code.to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
//!
|
||||
//! The Solidity compiler unit tests for unsupported opcodes.
|
||||
//!
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::solc::pipeline::Pipeline as SolcPipeline;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The `CODECOPY` instruction is not supported")]
|
||||
fn codecopy_yul_runtime() {
|
||||
let source_code = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract FixedCodeCopy {
|
||||
function copyCode() public view returns (bytes memory) {
|
||||
uint256 fixedCodeSize = 64;
|
||||
bytes memory code = new bytes(fixedCodeSize);
|
||||
|
||||
assembly {
|
||||
codecopy(add(code, 0x20), 0, fixedCodeSize)
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), source_code.to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
|
||||
pub const CALLCODE_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract CallcodeTest {
|
||||
function testCallcode(address target, bytes4 signature, uint256 inputValue) public returns (bool) {
|
||||
bool success;
|
||||
|
||||
assembly {
|
||||
let input := mload(0x40)
|
||||
mstore(input, signature)
|
||||
mstore(add(input, 0x04), inputValue)
|
||||
|
||||
let callResult := callcode(gas(), target, 0, input, 0x24, 0, 0)
|
||||
|
||||
success := and(callResult, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The `CALLCODE` instruction is not supported")]
|
||||
fn callcode_evmla() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), CALLCODE_TEST_SOURCE.to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::EVMLA,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The `CALLCODE` instruction is not supported")]
|
||||
fn callcode_yul() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), CALLCODE_TEST_SOURCE.to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The `PC` instruction is not supported")]
|
||||
fn pc_yul() {
|
||||
let source_code = r#"
|
||||
object "ProgramCounter" {
|
||||
code {
|
||||
datacopy(0, dataoffset("ProgramCounter_deployed"), datasize("ProgramCounter_deployed"))
|
||||
return(0, datasize("ProgramCounter_deployed"))
|
||||
}
|
||||
object "ProgramCounter_deployed" {
|
||||
code {
|
||||
function getPC() -> programCounter {
|
||||
programCounter := pc()
|
||||
}
|
||||
|
||||
let pcValue := getPC()
|
||||
sstore(0, pcValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
super::build_yul(source_code).expect("Test failure");
|
||||
}
|
||||
|
||||
pub const EXTCODECOPY_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract ExternalCodeCopy {
|
||||
function copyExternalCode(address target, uint256 codeSize) public view returns (bytes memory) {
|
||||
bytes memory code = new bytes(codeSize);
|
||||
|
||||
assembly {
|
||||
extcodecopy(target, add(code, 0x20), 0, codeSize)
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The `EXTCODECOPY` instruction is not supported")]
|
||||
fn extcodecopy_evmla() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), EXTCODECOPY_TEST_SOURCE.to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::EVMLA,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The `EXTCODECOPY` instruction is not supported")]
|
||||
fn extcodecopy_yul() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), EXTCODECOPY_TEST_SOURCE.to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
|
||||
pub const SELFDESTRUCT_TEST_SOURCE: &str = r#"
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract MinimalDestructible {
|
||||
address payable public owner;
|
||||
|
||||
constructor() {
|
||||
owner = payable(msg.sender);
|
||||
}
|
||||
|
||||
function destroy() public {
|
||||
require(msg.sender == owner, "Only the owner can call this function.");
|
||||
selfdestruct(owner);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")]
|
||||
fn selfdestruct_evmla() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), SELFDESTRUCT_TEST_SOURCE.to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::EVMLA,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")]
|
||||
fn selfdestruct_yul() {
|
||||
let mut sources = BTreeMap::new();
|
||||
sources.insert("test.sol".to_owned(), SELFDESTRUCT_TEST_SOURCE.to_owned());
|
||||
|
||||
super::build_solidity(
|
||||
sources,
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
SolcPipeline::Yul,
|
||||
era_compiler_llvm_context::OptimizerSettings::cycles(),
|
||||
)
|
||||
.expect("Test failure");
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//!
|
||||
//! The compiler warning.
|
||||
//!
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The compiler warning.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Warning {
|
||||
/// The warning for eponymous feature.
|
||||
EcRecover,
|
||||
/// The warning for eponymous feature.
|
||||
SendTransfer,
|
||||
/// The warning for eponymous feature.
|
||||
ExtCodeSize,
|
||||
/// The warning for eponymous feature.
|
||||
TxOrigin,
|
||||
/// The warning for eponymous feature.
|
||||
BlockTimestamp,
|
||||
/// The warning for eponymous feature.
|
||||
BlockNumber,
|
||||
/// The warning for eponymous feature.
|
||||
BlockHash,
|
||||
}
|
||||
|
||||
impl Warning {
|
||||
///
|
||||
/// Converts string arguments into an array of warnings.
|
||||
///
|
||||
pub fn try_from_strings(strings: &[String]) -> Result<Vec<Self>, anyhow::Error> {
|
||||
strings
|
||||
.iter()
|
||||
.map(|string| Self::from_str(string))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Warning {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"ecrecover" => Ok(Self::EcRecover),
|
||||
"sendtransfer" => Ok(Self::SendTransfer),
|
||||
"extcodesize" => Ok(Self::ExtCodeSize),
|
||||
"txorigin" => Ok(Self::TxOrigin),
|
||||
"blocktimestamp" => Ok(Self::BlockTimestamp),
|
||||
"blocknumber" => Ok(Self::BlockNumber),
|
||||
"blockhash" => Ok(Self::BlockHash),
|
||||
_ => Err(anyhow::anyhow!("Invalid warning: {}", string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//!
|
||||
//! The Yul IR error.
|
||||
//!
|
||||
|
||||
use crate::yul::lexer::error::Error as LexerError;
|
||||
use crate::yul::parser::error::Error as ParserError;
|
||||
|
||||
///
|
||||
/// The Yul IR error.
|
||||
///
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// The lexer error.
|
||||
#[error("Lexical error: {0}")]
|
||||
Lexer(#[from] LexerError),
|
||||
/// The parser error.
|
||||
#[error("Syntax error: {0}")]
|
||||
Parser(#[from] ParserError),
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//!
|
||||
//! The Yul IR lexer error.
|
||||
//!
|
||||
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
|
||||
///
|
||||
/// The Yul IR lexer error.
|
||||
///
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// The invalid lexeme error.
|
||||
#[error("{location} Invalid character sequence `{sequence}`")]
|
||||
InvalidLexeme {
|
||||
/// The lexeme location.
|
||||
location: Location,
|
||||
/// The invalid sequence of characters.
|
||||
sequence: String,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
//!
|
||||
//! The compiler lexer.
|
||||
//!
|
||||
|
||||
pub mod error;
|
||||
pub mod token;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use self::error::Error;
|
||||
use self::token::lexeme::comment::Comment;
|
||||
use self::token::lexeme::identifier::Identifier;
|
||||
use self::token::lexeme::literal::integer::Integer as IntegerLiteral;
|
||||
use self::token::lexeme::literal::string::String as StringLiteral;
|
||||
use self::token::lexeme::symbol::Symbol;
|
||||
use self::token::lexeme::Lexeme;
|
||||
use self::token::location::Location;
|
||||
use self::token::Token;
|
||||
|
||||
///
|
||||
/// The compiler lexer.
|
||||
///
|
||||
pub struct Lexer {
|
||||
/// The input source code.
|
||||
input: String,
|
||||
/// The number of characters processed so far.
|
||||
offset: usize,
|
||||
/// The current location.
|
||||
location: Location,
|
||||
/// The peeked lexeme, waiting to be fetched.
|
||||
peeked: Option<Token>,
|
||||
}
|
||||
|
||||
impl Lexer {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(mut input: String) -> Self {
|
||||
input.push('\n');
|
||||
|
||||
Self {
|
||||
input,
|
||||
offset: 0,
|
||||
location: Location::default(),
|
||||
peeked: None,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Advances the lexer, returning the next lexeme.
|
||||
///
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&mut self) -> Result<Token, Error> {
|
||||
if let Some(peeked) = self.peeked.take() {
|
||||
return Ok(peeked);
|
||||
}
|
||||
|
||||
while self.offset < self.input.len() {
|
||||
let input = &self.input[self.offset..];
|
||||
|
||||
if input.starts_with(|character| char::is_ascii_whitespace(&character)) {
|
||||
if input.starts_with('\n') {
|
||||
self.location.line += 1;
|
||||
self.location.column = 1;
|
||||
} else if !input.starts_with('\r') {
|
||||
self.location.column += 1;
|
||||
}
|
||||
self.offset += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(token) = Comment::parse(input) {
|
||||
self.offset += token.length;
|
||||
self.location
|
||||
.shift_down(token.location.line, token.location.column);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(mut token) = StringLiteral::parse(input) {
|
||||
token.location = self.location;
|
||||
|
||||
self.offset += token.length;
|
||||
self.location.shift_right(token.length);
|
||||
return Ok(token);
|
||||
}
|
||||
|
||||
if let Some(mut token) = IntegerLiteral::parse(input) {
|
||||
token.location = self.location;
|
||||
|
||||
self.offset += token.length;
|
||||
self.location.shift_right(token.length);
|
||||
return Ok(token);
|
||||
}
|
||||
|
||||
if let Some(mut token) = Identifier::parse(input) {
|
||||
token.location = self.location;
|
||||
|
||||
self.offset += token.length;
|
||||
self.location.shift_right(token.length);
|
||||
return Ok(token);
|
||||
}
|
||||
|
||||
if let Some(mut token) = Symbol::parse(input) {
|
||||
token.location = self.location;
|
||||
|
||||
self.offset += token.length;
|
||||
self.location.shift_right(token.length);
|
||||
return Ok(token);
|
||||
}
|
||||
|
||||
let end = self.input[self.offset..]
|
||||
.find(char::is_whitespace)
|
||||
.unwrap_or(self.input.len());
|
||||
return Err(Error::InvalidLexeme {
|
||||
location: self.location,
|
||||
sequence: self.input[self.offset..self.offset + end].to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Token::new(self.location, Lexeme::EndOfFile, 0))
|
||||
}
|
||||
|
||||
///
|
||||
/// Peeks the next lexeme without advancing the iterator.
|
||||
///
|
||||
pub fn peek(&mut self) -> Result<Token, Error> {
|
||||
match self.peeked {
|
||||
Some(ref peeked) => Ok(peeked.clone()),
|
||||
None => {
|
||||
let peeked = self.next()?;
|
||||
self.peeked = Some(peeked.clone());
|
||||
Ok(peeked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//!
|
||||
//! The Yul IR lexer tests.
|
||||
//!
|
||||
|
||||
use crate::yul::lexer::error::Error;
|
||||
use crate::yul::lexer::token::lexeme::Lexeme;
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
use crate::yul::lexer::Lexer;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let input = r#"
|
||||
object "Test" {
|
||||
code {
|
||||
{
|
||||
/*
|
||||
The deploy code.
|
||||
*/
|
||||
mstore(64, 128)
|
||||
if callvalue() { revert(0, 0) }
|
||||
let _1 := datasize("Test_deployed")
|
||||
codecopy(0, dataoffset("Test_deployed"), _1)
|
||||
return(0, _1)
|
||||
}
|
||||
}
|
||||
object "Test_deployed" {
|
||||
code {
|
||||
{
|
||||
/*
|
||||
The runtime code.
|
||||
*/
|
||||
mstore(64, 128)
|
||||
if iszero(lt(calldatasize(), 4))
|
||||
{
|
||||
let _1 := 0
|
||||
switch shr(224, calldataload(_1))
|
||||
case 0x3df4ddf4 {
|
||||
if callvalue() { revert(_1, _1) }
|
||||
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
|
||||
let memPos := allocate_memory(_1)
|
||||
mstore(memPos, 0x2a)
|
||||
return(memPos, 32)
|
||||
}
|
||||
case 0x5a8ac02d {
|
||||
if callvalue() { revert(_1, _1) }
|
||||
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
|
||||
let memPos_1 := allocate_memory(_1)
|
||||
return(memPos_1, sub(abi_encode_uint256(memPos_1, 0x63), memPos_1))
|
||||
}
|
||||
}
|
||||
revert(0, 0)
|
||||
}
|
||||
function abi_encode_uint256(headStart, value0) -> tail
|
||||
{
|
||||
tail := add(headStart, 32)
|
||||
mstore(headStart, value0)
|
||||
}
|
||||
function allocate_memory(size) -> memPtr
|
||||
{
|
||||
memPtr := mload(64)
|
||||
let newFreePtr := add(memPtr, and(add(size, 31), not(31)))
|
||||
if or(gt(newFreePtr, 0xffffffffffffffff)#, lt(newFreePtr, memPtr))
|
||||
{
|
||||
mstore(0, shl(224, 0x4e487b71))
|
||||
mstore(4, 0x41)
|
||||
revert(0, 0x24)
|
||||
}
|
||||
mstore(64, newFreePtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let mut lexer = Lexer::new(input.to_owned());
|
||||
loop {
|
||||
match lexer.next() {
|
||||
Ok(token) => assert_ne!(token.lexeme, Lexeme::EndOfFile),
|
||||
Err(error) => {
|
||||
assert_eq!(
|
||||
error,
|
||||
Error::InvalidLexeme {
|
||||
location: Location::new(51, 57),
|
||||
sequence: "#,".to_owned(),
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//!
|
||||
//! The comment lexeme.
|
||||
//!
|
||||
|
||||
pub mod multi_line;
|
||||
pub mod single_line;
|
||||
|
||||
use crate::yul::lexer::token::Token;
|
||||
|
||||
use self::multi_line::Comment as MultiLineComment;
|
||||
use self::single_line::Comment as SingleLineComment;
|
||||
|
||||
///
|
||||
/// The comment lexeme.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Comment {
|
||||
/// The single-line comment.
|
||||
SingleLine(SingleLineComment),
|
||||
/// The multi-line comment.
|
||||
MultiLine(MultiLineComment),
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
///
|
||||
/// Returns the comment's length, including the trimmed whitespace around it.
|
||||
///
|
||||
pub fn parse(input: &str) -> Option<Token> {
|
||||
if input.starts_with(SingleLineComment::START) {
|
||||
Some(SingleLineComment::parse(input))
|
||||
} else if input.starts_with(MultiLineComment::START) {
|
||||
Some(MultiLineComment::parse(input))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//!
|
||||
//! The multi-line comment lexeme.
|
||||
//!
|
||||
|
||||
use crate::yul::lexer::token::lexeme::Lexeme;
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
use crate::yul::lexer::token::Token;
|
||||
|
||||
///
|
||||
/// The multi-line comment lexeme.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Comment {}
|
||||
|
||||
impl Comment {
|
||||
/// The start symbol.
|
||||
pub const START: &'static str = "/*";
|
||||
/// The end symbol.
|
||||
pub const END: &'static str = "*/";
|
||||
|
||||
///
|
||||
/// Returns the comment, including its length and number of lines.
|
||||
///
|
||||
pub fn parse(input: &str) -> Token {
|
||||
let end_position = input.find(Self::END).unwrap_or(input.len());
|
||||
let input = &input[..end_position];
|
||||
|
||||
let length = end_position + Self::END.len();
|
||||
let lines = input.matches('\n').count();
|
||||
let columns = match input.rfind('\n') {
|
||||
Some(new_line) => end_position - (new_line + 1),
|
||||
None => end_position,
|
||||
};
|
||||
|
||||
Token::new(Location::new(lines, columns), Lexeme::Comment, length)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//!
|
||||
//! The single-line comment lexeme.
|
||||
//!
|
||||
|
||||
use crate::yul::lexer::token::lexeme::Lexeme;
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
use crate::yul::lexer::token::Token;
|
||||
|
||||
///
|
||||
/// The single-line comment lexeme.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Comment {}
|
||||
|
||||
impl Comment {
|
||||
/// The start symbol.
|
||||
pub const START: &'static str = "//";
|
||||
/// The end symbol.
|
||||
pub const END: &'static str = "\n";
|
||||
|
||||
///
|
||||
/// Returns the comment's length, including the trimmed whitespace around it.
|
||||
///
|
||||
pub fn parse(input: &str) -> Token {
|
||||
let end_position = input.find(Self::END).unwrap_or(input.len());
|
||||
let length = end_position + Self::END.len();
|
||||
|
||||
Token::new(Location::new(1, 1), Lexeme::Comment, length)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//!
|
||||
//! The identifier lexeme.
|
||||
//!
|
||||
|
||||
use crate::yul::lexer::token::lexeme::keyword::Keyword;
|
||||
use crate::yul::lexer::token::lexeme::Lexeme;
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
use crate::yul::lexer::token::Token;
|
||||
|
||||
///
|
||||
/// The identifier lexeme.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Identifier {
|
||||
/// The inner string.
|
||||
pub inner: String,
|
||||
}
|
||||
|
||||
impl Identifier {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(inner: String) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses the identifier, returning it as a token.
|
||||
///
|
||||
pub fn parse(input: &str) -> Option<Token> {
|
||||
if !input.starts_with(Self::can_begin) {
|
||||
return None;
|
||||
}
|
||||
let end = input.find(Self::cannot_continue).unwrap_or(input.len());
|
||||
|
||||
let inner = input[..end].to_string();
|
||||
let length = inner.len();
|
||||
|
||||
if let Some(token) = Keyword::parse(inner.as_str()) {
|
||||
return Some(token);
|
||||
}
|
||||
|
||||
Some(Token::new(
|
||||
Location::new(0, length),
|
||||
Lexeme::Identifier(Self::new(inner)),
|
||||
length,
|
||||
))
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character can begin an identifier.
|
||||
///
|
||||
pub fn can_begin(character: char) -> bool {
|
||||
character.is_alphabetic() || character == '_' || character == '$'
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character can continue an identifier.
|
||||
///
|
||||
pub fn can_continue(character: char) -> bool {
|
||||
Self::can_begin(character)
|
||||
|| character.is_numeric()
|
||||
|| character == '_'
|
||||
|| character == '$'
|
||||
|| character == '.'
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character cannot continue an identifier.
|
||||
///
|
||||
pub fn cannot_continue(character: char) -> bool {
|
||||
!Self::can_continue(character)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Identifier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
//!
|
||||
//! The keyword lexeme.
|
||||
//!
|
||||
|
||||
use crate::yul::lexer::token::lexeme::literal::boolean::Boolean as BooleanLiteral;
|
||||
use crate::yul::lexer::token::lexeme::literal::Literal;
|
||||
use crate::yul::lexer::token::lexeme::Lexeme;
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
use crate::yul::lexer::token::Token;
|
||||
|
||||
///
|
||||
/// The keyword lexeme.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Keyword {
|
||||
/// The `object` keyword.
|
||||
Object,
|
||||
/// The `code` keyword.
|
||||
Code,
|
||||
/// The `function` keyword.
|
||||
Function,
|
||||
/// The `let` keyword.
|
||||
Let,
|
||||
/// The `if` keyword.
|
||||
If,
|
||||
/// The `switch` keyword.
|
||||
Switch,
|
||||
/// The `case` keyword.
|
||||
Case,
|
||||
/// The `default` keyword.
|
||||
Default,
|
||||
/// The `for` keyword.
|
||||
For,
|
||||
/// The `break` keyword.
|
||||
Break,
|
||||
/// The `continue` keyword.
|
||||
Continue,
|
||||
/// The `leave` keyword.
|
||||
Leave,
|
||||
/// The `true` keyword.
|
||||
True,
|
||||
/// The `false` keyword.
|
||||
False,
|
||||
/// The `bool` keyword.
|
||||
Bool,
|
||||
/// The `int{N}` keyword.
|
||||
Int(usize),
|
||||
/// The `uint{N}` keyword.
|
||||
Uint(usize),
|
||||
}
|
||||
|
||||
impl Keyword {
|
||||
///
|
||||
/// Parses the keyword, returning it as a token.
|
||||
///
|
||||
pub fn parse(input: &str) -> Option<Token> {
|
||||
let keyword = Self::parse_keyword(input)?;
|
||||
let lexeme = match BooleanLiteral::try_from(keyword) {
|
||||
Ok(literal) => Lexeme::Literal(Literal::Boolean(literal)),
|
||||
Err(keyword) => Lexeme::Keyword(keyword),
|
||||
};
|
||||
|
||||
let length = lexeme.to_string().len();
|
||||
if length != input.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Token::new(Location::new(0, length), lexeme, length))
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses the keyword itself.
|
||||
///
|
||||
fn parse_keyword(input: &str) -> Option<Self> {
|
||||
if !input.starts_with(Self::can_begin) {
|
||||
return None;
|
||||
}
|
||||
let end = input.find(Self::cannot_continue).unwrap_or(input.len());
|
||||
let input = &input[..end];
|
||||
|
||||
if let Some(input) = input.strip_prefix("int") {
|
||||
if let Ok(bitlength) = input.parse::<usize>() {
|
||||
return Some(Self::Int(bitlength));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(input) = input.strip_prefix("uint") {
|
||||
if let Ok(bitlength) = input.parse::<usize>() {
|
||||
return Some(Self::Uint(bitlength));
|
||||
}
|
||||
}
|
||||
|
||||
Some(match input {
|
||||
"object" => Self::Object,
|
||||
"code" => Self::Code,
|
||||
"function" => Self::Function,
|
||||
"let" => Self::Let,
|
||||
"if" => Self::If,
|
||||
"switch" => Self::Switch,
|
||||
"case" => Self::Case,
|
||||
"default" => Self::Default,
|
||||
"for" => Self::For,
|
||||
"break" => Self::Break,
|
||||
"continue" => Self::Continue,
|
||||
"leave" => Self::Leave,
|
||||
"true" => Self::True,
|
||||
"false" => Self::False,
|
||||
"bool" => Self::Bool,
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character can begin a keyword.
|
||||
///
|
||||
pub fn can_begin(character: char) -> bool {
|
||||
character.is_alphabetic()
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character can continue a keyword.
|
||||
///
|
||||
pub fn can_continue(character: char) -> bool {
|
||||
Self::can_begin(character) || character.is_numeric()
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character cannot continue a keyword.
|
||||
///
|
||||
pub fn cannot_continue(character: char) -> bool {
|
||||
!Self::can_continue(character)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Keyword {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Object => write!(f, "object"),
|
||||
Self::Code => write!(f, "code"),
|
||||
Self::Function => write!(f, "function"),
|
||||
Self::Let => write!(f, "let"),
|
||||
Self::If => write!(f, "if"),
|
||||
Self::Switch => write!(f, "switch"),
|
||||
Self::Case => write!(f, "case"),
|
||||
Self::Default => write!(f, "default"),
|
||||
Self::For => write!(f, "for"),
|
||||
Self::Break => write!(f, "break"),
|
||||
Self::Continue => write!(f, "continue"),
|
||||
Self::Leave => write!(f, "leave"),
|
||||
Self::True => write!(f, "true"),
|
||||
Self::False => write!(f, "false"),
|
||||
Self::Bool => write!(f, "bool"),
|
||||
Self::Int(bitlength) => write!(f, "int{bitlength}"),
|
||||
Self::Uint(bitlength) => write!(f, "uint{bitlength}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//!
|
||||
//! The boolean literal lexeme.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::yul::lexer::token::lexeme::keyword::Keyword;
|
||||
|
||||
///
|
||||
/// The boolean literal lexeme.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum Boolean {
|
||||
/// Created from the `false` keyword.
|
||||
False,
|
||||
/// Created from the `true` keyword.
|
||||
True,
|
||||
}
|
||||
|
||||
impl Boolean {
|
||||
///
|
||||
/// Creates a `false` value.
|
||||
///
|
||||
pub fn r#false() -> Self {
|
||||
Self::False
|
||||
}
|
||||
|
||||
///
|
||||
/// Creates a `true` value.
|
||||
///
|
||||
pub fn r#true() -> Self {
|
||||
Self::True
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Keyword> for Boolean {
|
||||
type Error = Keyword;
|
||||
|
||||
fn try_from(keyword: Keyword) -> Result<Self, Self::Error> {
|
||||
Ok(match keyword {
|
||||
Keyword::False => Self::False,
|
||||
Keyword::True => Self::True,
|
||||
unknown => return Err(unknown),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Boolean {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
Self::True
|
||||
} else {
|
||||
Self::False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Boolean {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::False => write!(f, "false"),
|
||||
Self::True => write!(f, "true"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//!
|
||||
//! The integer literal lexeme.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::yul::lexer::token::lexeme::Lexeme;
|
||||
use crate::yul::lexer::token::lexeme::Literal;
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
use crate::yul::lexer::token::Token;
|
||||
|
||||
///
|
||||
/// The integer literal lexeme.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum Integer {
|
||||
/// An integer literal, like `42`.
|
||||
Decimal {
|
||||
/// The inner literal contents.
|
||||
inner: String,
|
||||
},
|
||||
/// A hexadecimal literal, like `0xffff`.
|
||||
Hexadecimal {
|
||||
/// The inner literal contents.
|
||||
inner: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Integer {
|
||||
///
|
||||
/// Creates a decimal value.
|
||||
///
|
||||
pub fn new_decimal(inner: String) -> Self {
|
||||
Self::Decimal { inner }
|
||||
}
|
||||
|
||||
///
|
||||
/// Creates a hexadecimal value.
|
||||
///
|
||||
pub fn new_hexadecimal(inner: String) -> Self {
|
||||
Self::Hexadecimal { inner }
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses the value from the source code slice.
|
||||
///
|
||||
pub fn parse(input: &str) -> Option<Token> {
|
||||
let (value, length) = if let Some(body) = input.strip_prefix("0x") {
|
||||
let end = body
|
||||
.find(Self::cannot_continue_hexadecimal)
|
||||
.unwrap_or(body.len());
|
||||
let length = "0x".len() + end;
|
||||
let value = Self::new_hexadecimal(input[..length].to_owned());
|
||||
(value, length)
|
||||
} else if input.starts_with(Self::can_begin_decimal) {
|
||||
let end = input
|
||||
.find(Self::cannot_continue_decimal)
|
||||
.unwrap_or(input.len());
|
||||
let length = end;
|
||||
let value = Self::new_decimal(input[..length].to_owned());
|
||||
(value, length)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let token = Token::new(
|
||||
Location::new(0, length),
|
||||
Lexeme::Literal(Literal::Integer(value)),
|
||||
length,
|
||||
);
|
||||
Some(token)
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character can begin a decimal number.
|
||||
///
|
||||
pub fn can_begin_decimal(character: char) -> bool {
|
||||
Self::can_continue_decimal(character)
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character can continue a decimal number.
|
||||
///
|
||||
pub fn can_continue_decimal(character: char) -> bool {
|
||||
character.is_digit(era_compiler_common::BASE_DECIMAL)
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character cannot continue a decimal number.
|
||||
///
|
||||
pub fn cannot_continue_decimal(character: char) -> bool {
|
||||
!Self::can_continue_decimal(character)
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character can continue a hexadecimal number.
|
||||
///
|
||||
pub fn can_continue_hexadecimal(character: char) -> bool {
|
||||
character.is_digit(era_compiler_common::BASE_HEXADECIMAL)
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the character cannot continue a hexadecimal number.
|
||||
///
|
||||
pub fn cannot_continue_hexadecimal(character: char) -> bool {
|
||||
!Self::can_continue_hexadecimal(character)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Integer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Decimal { inner } => write!(f, "{inner}"),
|
||||
Self::Hexadecimal { inner } => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//!
|
||||
//! The literal lexeme.
|
||||
//!
|
||||
|
||||
pub mod boolean;
|
||||
pub mod integer;
|
||||
pub mod string;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::boolean::Boolean;
|
||||
use self::integer::Integer;
|
||||
use self::string::String;
|
||||
|
||||
///
|
||||
/// The literal lexeme.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum Literal {
|
||||
/// A boolean literal, like `true`, or `false`.
|
||||
Boolean(Boolean),
|
||||
/// An integer literal, like `42`, or `0xff`.
|
||||
Integer(Integer),
|
||||
/// A string literal, like `"message"`.
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Literal {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Boolean(inner) => write!(f, "{inner}"),
|
||||
Self::Integer(inner) => write!(f, "{inner}"),
|
||||
Self::String(inner) => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//!
|
||||
//! The string literal lexeme.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::yul::lexer::token::lexeme::Lexeme;
|
||||
use crate::yul::lexer::token::lexeme::Literal;
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
use crate::yul::lexer::token::Token;
|
||||
|
||||
///
|
||||
/// The string literal lexeme.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct String {
|
||||
/// The inner string contents.
|
||||
pub inner: std::string::String,
|
||||
/// Whether the string is hexadecimal.
|
||||
pub is_hexadecimal: bool,
|
||||
}
|
||||
|
||||
impl String {
|
||||
///
|
||||
/// Creates a string literal value.
|
||||
///
|
||||
pub fn new(inner: ::std::string::String, is_hexadecimal: bool) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
is_hexadecimal,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses the value from the source code slice.
|
||||
///
|
||||
pub fn parse(input: &str) -> Option<Token> {
|
||||
let mut length = 0;
|
||||
|
||||
let is_string = input[length..].starts_with('"');
|
||||
let is_hex_string = input[length..].starts_with(r#"hex""#);
|
||||
|
||||
if !is_string && !is_hex_string {
|
||||
return None;
|
||||
}
|
||||
|
||||
if is_string {
|
||||
length += 1;
|
||||
}
|
||||
if is_hex_string {
|
||||
length += r#"hex""#.len();
|
||||
}
|
||||
|
||||
let mut string = std::string::String::new();
|
||||
loop {
|
||||
if input[length..].starts_with('\\') {
|
||||
string.push(input.chars().nth(length).expect("Always exists"));
|
||||
string.push(input.chars().nth(length + 1).expect("Always exists"));
|
||||
length += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if input[length..].starts_with('"') {
|
||||
length += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
string.push(input.chars().nth(length).expect("Always exists"));
|
||||
length += 1;
|
||||
}
|
||||
|
||||
let string = string
|
||||
.strip_prefix('"')
|
||||
.and_then(|string| string.strip_suffix('"'))
|
||||
.unwrap_or(string.as_str())
|
||||
.to_owned();
|
||||
|
||||
let literal = Self::new(string, is_hex_string);
|
||||
|
||||
Some(Token::new(
|
||||
Location::new(0, length),
|
||||
Lexeme::Literal(Literal::String(literal)),
|
||||
length,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for String {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//!
|
||||
//! The lexeme.
|
||||
//!
|
||||
|
||||
pub mod comment;
|
||||
pub mod identifier;
|
||||
pub mod keyword;
|
||||
pub mod literal;
|
||||
pub mod symbol;
|
||||
|
||||
use self::identifier::Identifier;
|
||||
use self::keyword::Keyword;
|
||||
use self::literal::Literal;
|
||||
use self::symbol::Symbol;
|
||||
|
||||
///
|
||||
/// The lexeme.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Lexeme {
|
||||
/// The keyword lexeme.
|
||||
Keyword(Keyword),
|
||||
/// The symbol lexeme.
|
||||
Symbol(Symbol),
|
||||
/// The identifier lexeme.
|
||||
Identifier(Identifier),
|
||||
/// The literal lexeme.
|
||||
Literal(Literal),
|
||||
/// The comment lexeme.
|
||||
Comment,
|
||||
/// The end-of-file lexeme.
|
||||
EndOfFile,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Lexeme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Keyword(inner) => write!(f, "{inner}"),
|
||||
Self::Symbol(inner) => write!(f, "{inner}"),
|
||||
Self::Identifier(inner) => write!(f, "{inner}"),
|
||||
Self::Literal(inner) => write!(f, "{inner}"),
|
||||
Self::Comment => Ok(()),
|
||||
Self::EndOfFile => write!(f, "EOF"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//!
|
||||
//! The symbol lexeme.
|
||||
//!
|
||||
|
||||
use crate::yul::lexer::token::lexeme::Lexeme;
|
||||
use crate::yul::lexer::token::location::Location;
|
||||
use crate::yul::lexer::token::Token;
|
||||
|
||||
///
|
||||
/// The symbol lexeme.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Symbol {
|
||||
/// The `:=` symbol.
|
||||
Assignment,
|
||||
/// The `->` symbol.
|
||||
Arrow,
|
||||
/// The `{` symbol.
|
||||
BracketCurlyLeft,
|
||||
/// The `}` symbol.
|
||||
BracketCurlyRight,
|
||||
/// The `(` symbol.
|
||||
ParenthesisLeft,
|
||||
/// The `)` symbol.
|
||||
ParenthesisRight,
|
||||
/// The `,` symbol.
|
||||
Comma,
|
||||
/// The `:` symbol.
|
||||
Colon,
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
///
|
||||
/// Parses the symbol, returning it as a token.
|
||||
///
|
||||
pub fn parse(input: &str) -> Option<Token> {
|
||||
let (symbol, length) = match &input[..2] {
|
||||
":=" => (Self::Assignment, 2),
|
||||
"->" => (Self::Arrow, 2),
|
||||
|
||||
_ => match &input[..1] {
|
||||
"{" => (Self::BracketCurlyLeft, 1),
|
||||
"}" => (Self::BracketCurlyRight, 1),
|
||||
"(" => (Self::ParenthesisLeft, 1),
|
||||
")" => (Self::ParenthesisRight, 1),
|
||||
"," => (Self::Comma, 1),
|
||||
":" => (Self::Colon, 1),
|
||||
|
||||
_ => return None,
|
||||
},
|
||||
};
|
||||
|
||||
Some(Token::new(
|
||||
Location::new(0, length),
|
||||
Lexeme::Symbol(symbol),
|
||||
length,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Symbol {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Assignment => write!(f, ":="),
|
||||
Self::Arrow => write!(f, "->"),
|
||||
Self::BracketCurlyLeft => write!(f, "{{"),
|
||||
Self::BracketCurlyRight => write!(f, "}}"),
|
||||
Self::ParenthesisLeft => write!(f, "("),
|
||||
Self::ParenthesisRight => write!(f, ")"),
|
||||
Self::Comma => write!(f, ","),
|
||||
Self::Colon => write!(f, ":"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//!
|
||||
//! The lexical token location.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The token location in the source code file.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq)]
|
||||
pub struct Location {
|
||||
/// The line number, starting from 1.
|
||||
pub line: usize,
|
||||
/// The column number, starting from 1.
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl Default for Location {
|
||||
fn default() -> Self {
|
||||
Self { line: 1, column: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Location {
|
||||
///
|
||||
/// Creates a default location.
|
||||
///
|
||||
pub fn new(line: usize, column: usize) -> Self {
|
||||
Self { line, column }
|
||||
}
|
||||
|
||||
///
|
||||
/// Mutates the location by shifting the original one down by `lines` and
|
||||
/// setting the column to `column`.
|
||||
///
|
||||
pub fn shift_down(&mut self, lines: usize, column: usize) {
|
||||
if lines == 0 {
|
||||
self.shift_right(column);
|
||||
return;
|
||||
}
|
||||
|
||||
self.line += lines;
|
||||
self.column = column;
|
||||
}
|
||||
|
||||
///
|
||||
/// Mutates the location by shifting the original one rightward by `columns`.
|
||||
///
|
||||
pub fn shift_right(&mut self, columns: usize) {
|
||||
self.column += columns;
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Location {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.line == other.line && self.column == other.column
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Location {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}:{}", self.line, self.column)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//!
|
||||
//! The token.
|
||||
//!
|
||||
|
||||
pub mod lexeme;
|
||||
pub mod location;
|
||||
|
||||
use self::lexeme::Lexeme;
|
||||
use self::location::Location;
|
||||
|
||||
///
|
||||
/// The token.
|
||||
///
|
||||
/// Contains a lexeme and its location.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Token {
|
||||
/// The token location.
|
||||
pub location: Location,
|
||||
/// The lexeme.
|
||||
pub lexeme: Lexeme,
|
||||
/// The token length, including whitespaces.
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(location: Location, lexeme: Lexeme, length: usize) -> Self {
|
||||
Self {
|
||||
location,
|
||||
lexeme,
|
||||
length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Token {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}:{}", self.location, self.lexeme)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user