diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index 9ad98b5e..d8f55081 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -1592,6 +1592,7 @@ pub enum BytecodeObject { #[serde(deserialize_with = "serde_helpers::deserialize_bytes")] Bytecode(Bytes), /// Bytecode as hex string that's not fully linked yet and contains library placeholders + #[serde(with = "serde_helpers::string_bytes")] Unlinked(String), } @@ -1612,6 +1613,13 @@ impl BytecodeObject { BytecodeObject::Unlinked(_) => None, } } + /// Returns a reference to the underlying `String` if the object is unlinked + pub fn as_str(&self) -> Option<&str> { + match self { + BytecodeObject::Bytecode(_) => None, + BytecodeObject::Unlinked(s) => Some(s.as_str()), + } + } /// Returns the unlinked `String` if the object is unlinked or empty pub fn into_unlinked(self) -> Option { @@ -1715,10 +1723,10 @@ impl BytecodeObject { } } -// Returns a not deployable bytecode by default as "0x" +// Returns a not deployable bytecode by default as empty impl Default for BytecodeObject { fn default() -> Self { - BytecodeObject::Unlinked("0x".to_string()) + BytecodeObject::Unlinked("".to_string()) } } diff --git a/ethers-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs index 6aba852e..d2a6c151 100644 --- a/ethers-solc/src/artifacts/serde_helpers.rs +++ b/ethers-solc/src/artifacts/serde_helpers.rs @@ -77,6 +77,34 @@ pub mod json_string_opt { } } +/// serde support for string +pub mod string_bytes { + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &String, serializer: S) -> Result + where + S: Serializer, + { + if value.starts_with("0x") { + serializer.serialize_str(value.as_str()) + } else { + serializer.serialize_str(&format!("0x{}", value)) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + if let Some(rem) = value.strip_prefix("0x") { + Ok(rem.to_string()) + } else { + Ok(value) + } + } +} + pub mod display_from_str_opt { use serde::{de, Deserialize, Deserializer, Serializer}; use std::{fmt, str::FromStr}; diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs index f3c7ab46..4777c041 100644 --- a/ethers-solc/tests/project.rs +++ b/ethers-solc/tests/project.rs @@ -496,7 +496,7 @@ fn can_detect_type_error() { fn can_compile_single_files() { let tmp = TempProject::dapptools().unwrap(); - let foo = tmp + let f = tmp .add_contract( "examples/Foo", r#" @@ -507,7 +507,7 @@ fn can_compile_single_files() { ) .unwrap(); - let compiled = tmp.project().compile_file(foo.clone()).unwrap(); + let compiled = tmp.project().compile_file(f.clone()).unwrap(); assert!(!compiled.has_compiler_errors()); assert!(compiled.find("Foo").is_some()); @@ -522,8 +522,43 @@ fn can_compile_single_files() { ) .unwrap(); - let compiled = tmp.project().compile_files(vec![foo, bar]).unwrap(); + let compiled = tmp.project().compile_files(vec![f, bar]).unwrap(); assert!(!compiled.has_compiler_errors()); assert!(compiled.find("Foo").is_some()); assert!(compiled.find("Bar").is_some()); } + +#[test] +fn consistent_bytecode() { + let tmp = TempProject::dapptools().unwrap(); + + tmp.add_source( + "LinkTest", + r#" +// SPDX-License-Identifier: MIT +library LibTest { + function foobar(uint256 a) public view returns (uint256) { + return a * 100; + } +} +contract LinkTest { + function foo() public returns (uint256) { + return LibTest.foobar(1); + } +} +"#, + ) + .unwrap(); + + let compiled = tmp.compile().unwrap(); + assert!(!compiled.has_compiler_errors()); + + let contract = compiled.find("LinkTest").unwrap(); + let bytecode = &contract.bytecode.as_ref().unwrap().object; + assert!(bytecode.is_unlinked()); + let s = bytecode.as_str().unwrap(); + assert!(!s.starts_with("0x")); + + let s = serde_json::to_string(&bytecode).unwrap(); + assert_eq!(bytecode.clone(), serde_json::from_str(&s).unwrap()); +}