fix(solc): remapping aware libraries (#1190)
* feat(solc): add Libraries type * feat: add lib applied remappings * test: add lib linking tests * Update ethers-solc/src/artifacts/mod.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * Update ethers-solc/src/artifacts/mod.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * Update ethers-solc/src/artifacts/mod.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * Update ethers-solc/src/artifacts/mod.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * chore: rustfmt Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
parent
19f7a93243
commit
a978bc98af
|
@ -11,9 +11,12 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{compile::*, error::SolcIoError, remappings::Remapping, utils};
|
use crate::{
|
||||||
|
compile::*, error::SolcIoError, remappings::Remapping, utils, ProjectPathsConfig, SolcError,
|
||||||
|
};
|
||||||
|
|
||||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub use ast::*;
|
pub use ast::*;
|
||||||
|
@ -259,8 +262,15 @@ pub struct Settings {
|
||||||
pub via_ir: Option<bool>,
|
pub via_ir: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub debug: Option<DebuggingSettings>,
|
pub debug: Option<DebuggingSettings>,
|
||||||
#[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
|
/// Addresses of the libraries. If not all libraries are given here,
|
||||||
pub libraries: BTreeMap<String, BTreeMap<String, String>>,
|
/// it can result in unlinked objects whose output data is different.
|
||||||
|
///
|
||||||
|
/// The top level key is the name of the source file where the library is used.
|
||||||
|
/// If remappings are used, this source file should match the global path
|
||||||
|
/// after remappings were applied.
|
||||||
|
/// If this key is an empty string, that refers to a global level.
|
||||||
|
#[serde(default, skip_serializing_if = "Libraries::is_empty")]
|
||||||
|
pub libraries: Libraries,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
@ -381,6 +391,101 @@ impl Default for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper type for all libraries in the form of `<file>:<lib>:<addr>`
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Libraries {
|
||||||
|
/// All libraries, `(file path -> (Lib name -> Address))
|
||||||
|
pub libs: BTreeMap<PathBuf, BTreeMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl Libraries ===
|
||||||
|
|
||||||
|
impl Libraries {
|
||||||
|
/// Parses all libraries in the form of
|
||||||
|
/// `<file>:<lib>:<addr>`
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use ethers_solc::artifacts::Libraries;
|
||||||
|
/// let libs = Libraries::parse(&[
|
||||||
|
/// "src/DssSpell.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string(),
|
||||||
|
/// ])
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn parse(libs: &[String]) -> Result<Self, SolcError> {
|
||||||
|
let mut libraries = BTreeMap::default();
|
||||||
|
for lib in libs {
|
||||||
|
let mut items = lib.split(':');
|
||||||
|
let file = items.next().ok_or_else(|| {
|
||||||
|
SolcError::msg(format!("failed to parse path to library file: {}", lib))
|
||||||
|
})?;
|
||||||
|
let lib = items
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| SolcError::msg(format!("failed to parse library name: {}", lib)))?;
|
||||||
|
let addr = items.next().ok_or_else(|| {
|
||||||
|
SolcError::msg(format!("failed to parse library address: {}", lib))
|
||||||
|
})?;
|
||||||
|
if items.next().is_some() {
|
||||||
|
return Err(SolcError::msg(format!(
|
||||||
|
"failed to parse, too many arguments passed: {}",
|
||||||
|
lib
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
libraries
|
||||||
|
.entry(file.into())
|
||||||
|
.or_insert_with(BTreeMap::default)
|
||||||
|
.insert(lib.to_string(), addr.to_string());
|
||||||
|
}
|
||||||
|
Ok(Self { libs: libraries })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.libs.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.libs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Solc expects the lib paths to match the global path after remappings were applied
|
||||||
|
///
|
||||||
|
/// See also [ProjectPathsConfig::resolve_import]
|
||||||
|
pub fn with_applied_remappings(mut self, config: &ProjectPathsConfig) -> Self {
|
||||||
|
self.libs = self
|
||||||
|
.libs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(file, target)| {
|
||||||
|
let file = config.resolve_import(&config.root, &file).unwrap_or_else(|err| {
|
||||||
|
warn!(target: "libs", "Failed to resolve library `{}` for linking: {:?}", file.display(), err);
|
||||||
|
file
|
||||||
|
});
|
||||||
|
(file, target)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
|
||||||
|
fn from(libs: BTreeMap<PathBuf, BTreeMap<String, String>>) -> Self {
|
||||||
|
Self { libs }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
|
||||||
|
fn as_ref(&self) -> &BTreeMap<PathBuf, BTreeMap<String, String>> {
|
||||||
|
&self.libs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMut<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
|
||||||
|
fn as_mut(&mut self) -> &mut BTreeMap<PathBuf, BTreeMap<String, String>> {
|
||||||
|
&mut self.libs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Optimizer {
|
pub struct Optimizer {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
@ -772,13 +877,13 @@ pub struct Doc {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub kind: Option<String>,
|
pub kind: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub methods: Option<Libraries>,
|
pub methods: Option<DocLibraries>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub version: Option<u32>,
|
pub version: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
pub struct Libraries {
|
pub struct DocLibraries {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub libs: BTreeMap<String, serde_json::Value>,
|
pub libs: BTreeMap<String, serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
@ -1631,4 +1736,71 @@ mod tests {
|
||||||
let i = input.sanitized(&version);
|
let i = input.sanitized(&version);
|
||||||
assert!(i.settings.metadata.unwrap().bytecode_hash.is_none());
|
assert!(i.settings.metadata.unwrap().bytecode_hash.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_parse_libraries() {
|
||||||
|
let libraries = ["./src/lib/LibraryContract.sol:Library:0xaddress".to_string()];
|
||||||
|
|
||||||
|
let libs = Libraries::parse(&libraries[..]).unwrap().libs;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
libs,
|
||||||
|
BTreeMap::from([(
|
||||||
|
PathBuf::from("./src/lib/LibraryContract.sol"),
|
||||||
|
BTreeMap::from([("Library".to_string(), "0xaddress".to_string())])
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_parse_many_libraries() {
|
||||||
|
let libraries= [
|
||||||
|
"./src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
|
||||||
|
"./src/SizeAuction.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
|
||||||
|
"./src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
|
||||||
|
"./src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
|
||||||
|
"./src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let libs = Libraries::parse(&libraries[..]).unwrap().libs;
|
||||||
|
|
||||||
|
pretty_assertions::assert_eq!(
|
||||||
|
libs,
|
||||||
|
BTreeMap::from([
|
||||||
|
(
|
||||||
|
PathBuf::from("./src/SizeAuctionDiscount.sol"),
|
||||||
|
BTreeMap::from([
|
||||||
|
(
|
||||||
|
"Chainlink".to_string(),
|
||||||
|
"0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Math".to_string(),
|
||||||
|
"0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
|
||||||
|
)
|
||||||
|
])
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PathBuf::from("./src/SizeAuction.sol"),
|
||||||
|
BTreeMap::from([
|
||||||
|
(
|
||||||
|
"ChainlinkTWAP".to_string(),
|
||||||
|
"0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Math".to_string(),
|
||||||
|
"0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
|
||||||
|
)
|
||||||
|
])
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PathBuf::from("./src/test/ChainlinkTWAP.t.sol"),
|
||||||
|
BTreeMap::from([(
|
||||||
|
"ChainlinkTWAP".to_string(),
|
||||||
|
"0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
|
||||||
|
)])
|
||||||
|
),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -650,7 +650,7 @@ impl<T: Into<PathBuf>> From<T> for Solc {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::CompilerInput;
|
use crate::{Artifact, CompilerInput};
|
||||||
|
|
||||||
fn solc() -> Solc {
|
fn solc() -> Solc {
|
||||||
Solc::default()
|
Solc::default()
|
||||||
|
@ -692,6 +692,18 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_compile_with_remapped_links() {
|
||||||
|
let input: CompilerInput =
|
||||||
|
serde_json::from_str(include_str!("../../test-data/library-remapping-in.json"))
|
||||||
|
.unwrap();
|
||||||
|
let out = solc().compile(&input).unwrap();
|
||||||
|
let (_, mut contracts) = out.split();
|
||||||
|
let contract = contracts.remove("LinkTest").unwrap();
|
||||||
|
let bytecode = &contract.get_bytecode().unwrap().object;
|
||||||
|
assert!(!bytecode.is_unlinked());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn async_solc_compile_works() {
|
async fn async_solc_compile_works() {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"language": "Solidity",
|
||||||
|
"sources": {
|
||||||
|
"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/tmp_dappPyXsdD/lib/remapping/MyLib.sol": {
|
||||||
|
"content": "\n// SPDX-License-Identifier: MIT\nlibrary MyLib {\n function foobar(uint256 a) public view returns (uint256) {\n \treturn a * 100;\n }\n}\n"
|
||||||
|
},
|
||||||
|
"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/tmp_dappPyXsdD/src/LinkTest.sol": {
|
||||||
|
"content": "\n// SPDX-License-Identifier: MIT\nimport \"remapping/MyLib.sol\";\ncontract LinkTest {\n function foo() public returns (uint256) {\n return MyLib.foobar(1);\n }\n}\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"remappings": [
|
||||||
|
"remapping/=/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/tmp_dappPyXsdD/lib/remapping/"
|
||||||
|
],
|
||||||
|
"optimizer": {
|
||||||
|
"enabled": false,
|
||||||
|
"runs": 200
|
||||||
|
},
|
||||||
|
"outputSelection": {
|
||||||
|
"*": {
|
||||||
|
"": [
|
||||||
|
"ast"
|
||||||
|
],
|
||||||
|
"*": [
|
||||||
|
"abi",
|
||||||
|
"evm.bytecode",
|
||||||
|
"evm.deployedBytecode",
|
||||||
|
"evm.methodIdentifiers"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"evmVersion": "london",
|
||||||
|
"libraries": {
|
||||||
|
"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/tmp_dappPyXsdD/lib/remapping/MyLib.sol": {
|
||||||
|
"MyLib": "0x0000000000000000000000000000000000000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,15 @@
|
||||||
//! project tests
|
//! project tests
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
io,
|
io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use ethers_core::types::Address;
|
||||||
use ethers_solc::{
|
use ethers_solc::{
|
||||||
artifacts::BytecodeHash,
|
artifacts::{BytecodeHash, Libraries},
|
||||||
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
|
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
|
||||||
project_util::*,
|
project_util::*,
|
||||||
remappings::Remapping,
|
remappings::Remapping,
|
||||||
|
@ -155,7 +156,7 @@ fn can_compile_dapp_detect_changes_in_libs() {
|
||||||
project
|
project
|
||||||
.paths_mut()
|
.paths_mut()
|
||||||
.remappings
|
.remappings
|
||||||
.push(Remapping::from_str(&format!("remapping={}/", remapping.display())).unwrap());
|
.push(Remapping::from_str(&format!("remapping/={}/", remapping.display())).unwrap());
|
||||||
|
|
||||||
let src = project
|
let src = project
|
||||||
.add_source(
|
.add_source(
|
||||||
|
@ -814,6 +815,130 @@ contract LinkTest {
|
||||||
assert_eq!(bytecode.clone(), serde_json::from_str(&s).unwrap());
|
assert_eq!(bytecode.clone(), serde_json::from_str(&s).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_apply_libraries() {
|
||||||
|
let mut tmp = TempProject::dapptools().unwrap();
|
||||||
|
|
||||||
|
tmp.add_source(
|
||||||
|
"LinkTest",
|
||||||
|
r#"
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
import "./MyLib.sol";
|
||||||
|
contract LinkTest {
|
||||||
|
function foo() public returns (uint256) {
|
||||||
|
return MyLib.foobar(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lib = tmp
|
||||||
|
.add_source(
|
||||||
|
"MyLib",
|
||||||
|
r#"
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
library MyLib {
|
||||||
|
function foobar(uint256 a) public view returns (uint256) {
|
||||||
|
return a * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
|
||||||
|
assert!(compiled.find("MyLib").is_some());
|
||||||
|
let contract = compiled.find("LinkTest").unwrap();
|
||||||
|
let bytecode = &contract.bytecode.as_ref().unwrap().object;
|
||||||
|
assert!(bytecode.is_unlinked());
|
||||||
|
|
||||||
|
// provide the library settings to let solc link
|
||||||
|
tmp.project_mut().solc_config.settings.libraries = BTreeMap::from([(
|
||||||
|
lib,
|
||||||
|
BTreeMap::from([("MyLib".to_string(), format!("{:?}", Address::zero()))]),
|
||||||
|
)])
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
|
||||||
|
assert!(compiled.find("MyLib").is_some());
|
||||||
|
let contract = compiled.find("LinkTest").unwrap();
|
||||||
|
let bytecode = &contract.bytecode.as_ref().unwrap().object;
|
||||||
|
assert!(!bytecode.is_unlinked());
|
||||||
|
|
||||||
|
let libs = Libraries::parse(&[format!("./src/MyLib.sol:MyLib:{:?}", Address::zero())]).unwrap();
|
||||||
|
// provide the library settings to let solc link
|
||||||
|
tmp.project_mut().solc_config.settings.libraries = libs.with_applied_remappings(tmp.paths());
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
|
||||||
|
assert!(compiled.find("MyLib").is_some());
|
||||||
|
let contract = compiled.find("LinkTest").unwrap();
|
||||||
|
let bytecode = &contract.bytecode.as_ref().unwrap().object;
|
||||||
|
assert!(!bytecode.is_unlinked());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_apply_libraries_with_remappings() {
|
||||||
|
let mut tmp = TempProject::dapptools().unwrap();
|
||||||
|
|
||||||
|
let remapping = tmp.paths().libraries[0].join("remapping");
|
||||||
|
tmp.paths_mut()
|
||||||
|
.remappings
|
||||||
|
.push(Remapping::from_str(&format!("remapping/={}/", remapping.display())).unwrap());
|
||||||
|
|
||||||
|
tmp.add_source(
|
||||||
|
"LinkTest",
|
||||||
|
r#"
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
import "remapping/MyLib.sol";
|
||||||
|
contract LinkTest {
|
||||||
|
function foo() public returns (uint256) {
|
||||||
|
return MyLib.foobar(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
tmp.add_lib(
|
||||||
|
"remapping/MyLib",
|
||||||
|
r#"
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
library MyLib {
|
||||||
|
function foobar(uint256 a) public view returns (uint256) {
|
||||||
|
return a * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
|
||||||
|
assert!(compiled.find("MyLib").is_some());
|
||||||
|
let contract = compiled.find("LinkTest").unwrap();
|
||||||
|
let bytecode = &contract.bytecode.as_ref().unwrap().object;
|
||||||
|
assert!(bytecode.is_unlinked());
|
||||||
|
|
||||||
|
let libs =
|
||||||
|
Libraries::parse(&[format!("remapping/MyLib.sol:MyLib:{:?}", Address::zero())]).unwrap(); // provide the library settings to let solc link
|
||||||
|
tmp.project_mut().solc_config.settings.libraries = libs.with_applied_remappings(tmp.paths());
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
|
||||||
|
assert!(compiled.find("MyLib").is_some());
|
||||||
|
let contract = compiled.find("LinkTest").unwrap();
|
||||||
|
let bytecode = &contract.bytecode.as_ref().unwrap().object;
|
||||||
|
assert!(!bytecode.is_unlinked());
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn can_recompile_with_changes() {
|
fn can_recompile_with_changes() {
|
||||||
let mut tmp = TempProject::dapptools().unwrap();
|
let mut tmp = TempProject::dapptools().unwrap();
|
||||||
|
|
Loading…
Reference in New Issue