use crate::{
artifacts::{serde_helpers, FunctionDebugData, GeneratedSource, Offsets},
sourcemap::{self, SourceMap, SyntaxError},
utils,
};
use ethers_core::{abi::Address, types::Bytes};
use serde::{Deserialize, Serialize, Serializer};
use std::collections::BTreeMap;
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Bytecode {
#[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
pub function_debug_data: BTreeMap<String, FunctionDebugData>,
#[serde(serialize_with = "serialize_bytecode_without_prefix")]
pub object: BytecodeObject,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub opcodes: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_map: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub generated_sources: Vec<GeneratedSource>,
#[serde(default)]
pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CompactBytecode {
pub object: BytecodeObject,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_map: Option<String>,
#[serde(default)]
pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
}
impl CompactBytecode {
pub fn empty() -> Self {
Self { object: Default::default(), source_map: None, link_references: Default::default() }
}
pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
self.source_map.as_ref().map(|map| sourcemap::parse(map))
}
pub fn link(
&mut self,
file: impl AsRef<str>,
library: impl AsRef<str>,
address: Address,
) -> bool {
if !self.object.is_unlinked() {
return true
}
let file = file.as_ref();
let library = library.as_ref();
if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
if contracts.remove(library).is_some() {
self.object.link(file, library, address);
}
if !contracts.is_empty() {
self.link_references.insert(key, contracts);
}
if self.link_references.is_empty() {
return self.object.resolve().is_some()
}
}
false
}
}
impl From<Bytecode> for CompactBytecode {
fn from(bcode: Bytecode) -> CompactBytecode {
CompactBytecode {
object: bcode.object,
source_map: bcode.source_map,
link_references: bcode.link_references,
}
}
}
impl From<CompactBytecode> for Bytecode {
fn from(bcode: CompactBytecode) -> Bytecode {
Bytecode {
object: bcode.object,
source_map: bcode.source_map,
link_references: bcode.link_references,
function_debug_data: Default::default(),
opcodes: Default::default(),
generated_sources: Default::default(),
}
}
}
impl From<BytecodeObject> for Bytecode {
fn from(object: BytecodeObject) -> Bytecode {
Bytecode {
object,
function_debug_data: Default::default(),
opcodes: Default::default(),
source_map: Default::default(),
generated_sources: Default::default(),
link_references: Default::default(),
}
}
}
impl Bytecode {
pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
self.source_map.as_ref().map(|map| sourcemap::parse(map))
}
pub fn link_fully_qualified(&mut self, name: impl AsRef<str>, addr: Address) -> bool {
if let Some((file, lib)) = name.as_ref().split_once(':') {
self.link(file, lib, addr)
} else {
false
}
}
pub fn link(
&mut self,
file: impl AsRef<str>,
library: impl AsRef<str>,
address: Address,
) -> bool {
if !self.object.is_unlinked() {
return true
}
let file = file.as_ref();
let library = library.as_ref();
if let Some((key, mut contracts)) = self.link_references.remove_entry(file) {
if contracts.remove(library).is_some() {
self.object.link(file, library, address);
}
if !contracts.is_empty() {
self.link_references.insert(key, contracts);
}
if self.link_references.is_empty() {
return self.object.resolve().is_some()
}
}
false
}
pub fn link_all<I, S, T>(&mut self, libs: I) -> bool
where
I: IntoIterator<Item = (S, T, Address)>,
S: AsRef<str>,
T: AsRef<str>,
{
for (file, lib, addr) in libs.into_iter() {
if self.link(file, lib, addr) {
return true
}
}
false
}
pub fn link_all_fully_qualified<I, S>(&mut self, libs: I) -> bool
where
I: IntoIterator<Item = (S, Address)>,
S: AsRef<str>,
{
for (name, addr) in libs.into_iter() {
if self.link_fully_qualified(name, addr) {
return true
}
}
false
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum BytecodeObject {
#[serde(deserialize_with = "serde_helpers::deserialize_bytes")]
Bytecode(Bytes),
#[serde(with = "serde_helpers::string_bytes")]
Unlinked(String),
}
impl BytecodeObject {
pub fn into_bytes(self) -> Option<Bytes> {
match self {
BytecodeObject::Bytecode(bytes) => Some(bytes),
BytecodeObject::Unlinked(_) => None,
}
}
pub fn as_bytes(&self) -> Option<&Bytes> {
match self {
BytecodeObject::Bytecode(bytes) => Some(bytes),
BytecodeObject::Unlinked(_) => None,
}
}
pub fn bytes_len(&self) -> usize {
self.as_bytes().map(|b| b.as_ref().len()).unwrap_or_default()
}
pub fn as_str(&self) -> Option<&str> {
match self {
BytecodeObject::Bytecode(_) => None,
BytecodeObject::Unlinked(s) => Some(s.as_str()),
}
}
pub fn into_unlinked(self) -> Option<String> {
match self {
BytecodeObject::Bytecode(_) => None,
BytecodeObject::Unlinked(code) => Some(code),
}
}
pub fn is_unlinked(&self) -> bool {
matches!(self, BytecodeObject::Unlinked(_))
}
pub fn is_bytecode(&self) -> bool {
matches!(self, BytecodeObject::Bytecode(_))
}
pub fn is_non_empty_bytecode(&self) -> bool {
self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default()
}
pub fn resolve(&mut self) -> Option<&Bytes> {
if let BytecodeObject::Unlinked(unlinked) = self {
if let Ok(linked) = hex::decode(unlinked) {
*self = BytecodeObject::Bytecode(linked.into());
}
}
self.as_bytes()
}
pub fn link_fully_qualified(&mut self, name: impl AsRef<str>, addr: Address) -> &mut Self {
if let BytecodeObject::Unlinked(ref mut unlinked) = self {
let name = name.as_ref();
let place_holder = utils::library_hash_placeholder(name);
let hex_addr = hex::encode(addr);
let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
*unlinked = unlinked
.replace(&format!("__{fully_qualified_placeholder}__"), &hex_addr)
.replace(&format!("__{place_holder}__"), &hex_addr)
}
self
}
pub fn link(
&mut self,
file: impl AsRef<str>,
library: impl AsRef<str>,
addr: Address,
) -> &mut Self {
self.link_fully_qualified(format!("{}:{}", file.as_ref(), library.as_ref(),), addr)
}
pub fn link_all<I, S, T>(&mut self, libs: I) -> &mut Self
where
I: IntoIterator<Item = (S, T, Address)>,
S: AsRef<str>,
T: AsRef<str>,
{
for (file, lib, addr) in libs.into_iter() {
self.link(file, lib, addr);
}
self
}
pub fn contains_fully_qualified_placeholder(&self, name: impl AsRef<str>) -> bool {
if let BytecodeObject::Unlinked(unlinked) = self {
let name = name.as_ref();
unlinked.contains(&utils::library_hash_placeholder(name)) ||
unlinked.contains(&utils::library_fully_qualified_placeholder(name))
} else {
false
}
}
pub fn contains_placeholder(&self, file: impl AsRef<str>, library: impl AsRef<str>) -> bool {
self.contains_fully_qualified_placeholder(format!("{}:{}", file.as_ref(), library.as_ref()))
}
}
impl Default for BytecodeObject {
fn default() -> Self {
BytecodeObject::Bytecode(Default::default())
}
}
impl AsRef<[u8]> for BytecodeObject {
fn as_ref(&self) -> &[u8] {
match self {
BytecodeObject::Bytecode(code) => code.as_ref(),
BytecodeObject::Unlinked(code) => code.as_bytes(),
}
}
}
pub fn serialize_bytecode_without_prefix<S>(
bytecode: &BytecodeObject,
s: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match bytecode {
BytecodeObject::Bytecode(code) => s.serialize_str(&hex::encode(code)),
BytecodeObject::Unlinked(code) => {
s.serialize_str(code.strip_prefix("0x").unwrap_or(code.as_str()))
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct DeployedBytecode {
#[serde(flatten)]
pub bytecode: Option<Bytecode>,
#[serde(
default,
rename = "immutableReferences",
skip_serializing_if = "::std::collections::BTreeMap::is_empty"
)]
pub immutable_references: BTreeMap<String, Vec<Offsets>>,
}
impl DeployedBytecode {
pub fn into_bytes(self) -> Option<Bytes> {
self.bytecode?.object.into_bytes()
}
}
impl From<Bytecode> for DeployedBytecode {
fn from(bcode: Bytecode) -> DeployedBytecode {
DeployedBytecode { bytecode: Some(bcode), immutable_references: Default::default() }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CompactDeployedBytecode {
#[serde(flatten)]
pub bytecode: Option<CompactBytecode>,
#[serde(
default,
rename = "immutableReferences",
skip_serializing_if = "::std::collections::BTreeMap::is_empty"
)]
pub immutable_references: BTreeMap<String, Vec<Offsets>>,
}
impl CompactDeployedBytecode {
pub fn empty() -> Self {
Self { bytecode: Some(CompactBytecode::empty()), immutable_references: Default::default() }
}
pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
self.bytecode.as_ref().and_then(|bytecode| bytecode.source_map())
}
}
impl From<DeployedBytecode> for CompactDeployedBytecode {
fn from(bcode: DeployedBytecode) -> CompactDeployedBytecode {
CompactDeployedBytecode {
bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
immutable_references: bcode.immutable_references,
}
}
}
impl From<CompactDeployedBytecode> for DeployedBytecode {
fn from(bcode: CompactDeployedBytecode) -> DeployedBytecode {
DeployedBytecode {
bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()),
immutable_references: bcode.immutable_references,
}
}
}
#[cfg(test)]
mod tests {
use crate::{artifacts::ContractBytecode, ConfigurableContractArtifact};
#[test]
fn test_empty_bytecode() {
let empty = r#"
{
"abi": [],
"bytecode": {
"object": "0x",
"linkReferences": {}
},
"deployedBytecode": {
"object": "0x",
"linkReferences": {}
}
}
"#;
let artifact: ConfigurableContractArtifact = serde_json::from_str(empty).unwrap();
let contract = artifact.into_contract_bytecode();
let bytecode: ContractBytecode = contract.into();
let bytecode = bytecode.unwrap();
assert!(!bytecode.bytecode.object.is_unlinked());
}
}