feat(solc): emit artifacts for standalone source files (#1296)
* feat(solc): emit artifact files for sources with no contracts * test(solc): add tests for emitting standalone sources * chore: update CHANGELOG * style: check ast is some
This commit is contained in:
parent
cffcc7bbec
commit
75835a9280
|
@ -96,6 +96,8 @@
|
||||||
|
|
||||||
- Bundle svm, svm-builds and sha2 dependencies in new `svm-solc` feature
|
- Bundle svm, svm-builds and sha2 dependencies in new `svm-solc` feature
|
||||||
[#1071](https://github.com/gakonst/ethers-rs/pull/1071)
|
[#1071](https://github.com/gakonst/ethers-rs/pull/1071)
|
||||||
|
- Emit artifact files for source files without any ContractDefinition
|
||||||
|
[#1296](https://github.com/gakonst/ethers-rs/pull/1296)
|
||||||
- Wrap `ethabi::Contract` into new type `LosslessAbi` and `abi: Option<Abi>` with `abi: Option<LosslessAbi>` in `ConfigurableContractArtifact`
|
- Wrap `ethabi::Contract` into new type `LosslessAbi` and `abi: Option<Abi>` with `abi: Option<LosslessAbi>` in `ConfigurableContractArtifact`
|
||||||
[#952](https://github.com/gakonst/ethers-rs/pull/952)
|
[#952](https://github.com/gakonst/ethers-rs/pull/952)
|
||||||
- Let `Project` take ownership of `ArtifactOutput` and change trait interface
|
- Let `Project` take ownership of `ArtifactOutput` and change trait interface
|
||||||
|
|
|
@ -11,6 +11,7 @@ use crate::{
|
||||||
Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates,
|
Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates,
|
||||||
GeneratedSource, LosslessAbi, Metadata, Offsets, Settings, StorageLayout, UserDoc,
|
GeneratedSource, LosslessAbi, Metadata, Offsets, Settings, StorageLayout, UserDoc,
|
||||||
},
|
},
|
||||||
|
sources::VersionedSourceFile,
|
||||||
ArtifactOutput, SolcConfig, SolcError, SourceFile,
|
ArtifactOutput, SolcConfig, SolcError, SourceFile,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -329,6 +330,18 @@ impl ArtifactOutput for ConfigurableArtifacts {
|
||||||
generated_sources: generated_sources.unwrap_or_default(),
|
generated_sources: generated_sources.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn standalone_source_file_to_artifact(
|
||||||
|
&self,
|
||||||
|
_path: &str,
|
||||||
|
file: &VersionedSourceFile,
|
||||||
|
) -> Option<Self::Artifact> {
|
||||||
|
file.source_file.ast.clone().map(|ast| ConfigurableContractArtifact {
|
||||||
|
id: Some(file.source_file.id),
|
||||||
|
ast: Some(ast),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the additional values to include in the contract's artifact file
|
/// Determines the additional values to include in the contract's artifact file
|
||||||
|
|
|
@ -9,7 +9,7 @@ use semver::Version;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::btree_map::BTreeMap,
|
collections::{btree_map::BTreeMap, HashSet},
|
||||||
fmt, fs, io,
|
fmt, fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
compile::output::{contracts::VersionedContracts, sources::VersionedSourceFiles},
|
compile::output::{contracts::VersionedContracts, sources::VersionedSourceFiles},
|
||||||
sourcemap::{SourceMap, SyntaxError},
|
sourcemap::{SourceMap, SyntaxError},
|
||||||
|
sources::VersionedSourceFile,
|
||||||
};
|
};
|
||||||
pub use configurable::*;
|
pub use configurable::*;
|
||||||
|
|
||||||
|
@ -632,14 +633,25 @@ pub trait ArtifactOutput {
|
||||||
sources: &VersionedSourceFiles,
|
sources: &VersionedSourceFiles,
|
||||||
) -> Artifacts<Self::Artifact> {
|
) -> Artifacts<Self::Artifact> {
|
||||||
let mut artifacts = ArtifactsMap::new();
|
let mut artifacts = ArtifactsMap::new();
|
||||||
|
|
||||||
|
// this tracks all the `SourceFile`s that we successfully mapped to a contract
|
||||||
|
let mut non_standalone_sources = HashSet::new();
|
||||||
|
|
||||||
|
// loop over all files and their contracts
|
||||||
for (file, contracts) in contracts.as_ref().iter() {
|
for (file, contracts) in contracts.as_ref().iter() {
|
||||||
let mut entries = BTreeMap::new();
|
let mut entries = BTreeMap::new();
|
||||||
|
|
||||||
|
// loop over all contracts and their versions
|
||||||
for (name, versioned_contracts) in contracts {
|
for (name, versioned_contracts) in contracts {
|
||||||
let mut contracts = Vec::with_capacity(versioned_contracts.len());
|
let mut contracts = Vec::with_capacity(versioned_contracts.len());
|
||||||
// check if the same contract compiled with multiple solc versions
|
// check if the same contract compiled with multiple solc versions
|
||||||
for contract in versioned_contracts {
|
for contract in versioned_contracts {
|
||||||
let source_file = sources.find_file_and_version(file, &contract.version);
|
let source_file = sources.find_file_and_version(file, &contract.version);
|
||||||
|
|
||||||
|
if let Some(source) = source_file {
|
||||||
|
non_standalone_sources.insert((source.id, &contract.version));
|
||||||
|
}
|
||||||
|
|
||||||
let artifact_path = if versioned_contracts.len() > 1 {
|
let artifact_path = if versioned_contracts.len() > 1 {
|
||||||
Self::output_file_versioned(file, name, &contract.version)
|
Self::output_file_versioned(file, name, &contract.version)
|
||||||
} else {
|
} else {
|
||||||
|
@ -664,8 +676,67 @@ pub trait ArtifactOutput {
|
||||||
artifacts.insert(file.to_string(), entries);
|
artifacts.insert(file.to_string(), entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extend with standalone source files and convert them to artifacts
|
||||||
|
for (file, sources) in sources.as_ref().iter() {
|
||||||
|
for source in sources {
|
||||||
|
if !non_standalone_sources.contains(&(source.source_file.id, &source.version)) {
|
||||||
|
// scan the ast as a safe measure to ensure this file does not include any
|
||||||
|
// source units
|
||||||
|
// there's also no need to create a standalone artifact for source files that
|
||||||
|
// don't contain an ast
|
||||||
|
if source.source_file.contains_contract_definition() ||
|
||||||
|
source.source_file.ast.is_none()
|
||||||
|
{
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// we use file and file stem
|
||||||
|
if let Some(name) = Path::new(file).file_stem().and_then(|stem| stem.to_str()) {
|
||||||
|
if let Some(artifact) =
|
||||||
|
self.standalone_source_file_to_artifact(file, source)
|
||||||
|
{
|
||||||
|
let artifact_path = if sources.len() > 1 {
|
||||||
|
Self::output_file_versioned(file, name, &source.version)
|
||||||
|
} else {
|
||||||
|
Self::output_file(file, name)
|
||||||
|
};
|
||||||
|
|
||||||
|
let entries = artifacts
|
||||||
|
.entry(file.to_string())
|
||||||
|
.or_default()
|
||||||
|
.entry(name.to_string())
|
||||||
|
.or_default();
|
||||||
|
|
||||||
|
if entries.iter().all(|entry| entry.version != source.version) {
|
||||||
|
entries.push(ArtifactFile {
|
||||||
|
artifact,
|
||||||
|
file: artifact_path,
|
||||||
|
version: source.version.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Artifacts(artifacts)
|
Artifacts(artifacts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This converts a `SourceFile` that doesn't contain _any_ contract definitions (interfaces,
|
||||||
|
/// contracts, libraries) to an artifact.
|
||||||
|
///
|
||||||
|
/// We do this because not all `SourceFile`s emitted by solc have at least 1 corresponding entry
|
||||||
|
/// in the `contracts`
|
||||||
|
/// section of the solc output. For example for an `errors.sol` that only contains custom error
|
||||||
|
/// definitions and no contract, no `Contract` object will be generated by solc. However, we
|
||||||
|
/// still want to emit an `Artifact` for that file that may include the `ast`, docs etc.,
|
||||||
|
/// because other tools depend on this, such as slither.
|
||||||
|
fn standalone_source_file_to_artifact(
|
||||||
|
&self,
|
||||||
|
_path: &str,
|
||||||
|
_file: &VersionedSourceFile,
|
||||||
|
) -> Option<Self::Artifact>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An `Artifact` implementation that uses a compact representation
|
/// An `Artifact` implementation that uses a compact representation
|
||||||
|
@ -695,6 +766,14 @@ impl ArtifactOutput for MinimalCombinedArtifacts {
|
||||||
) -> Self::Artifact {
|
) -> Self::Artifact {
|
||||||
Self::Artifact::from(contract)
|
Self::Artifact::from(contract)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn standalone_source_file_to_artifact(
|
||||||
|
&self,
|
||||||
|
_path: &str,
|
||||||
|
_file: &VersionedSourceFile,
|
||||||
|
) -> Option<Self::Artifact> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An Artifacts handler implementation that works the same as `MinimalCombinedArtifacts` but also
|
/// An Artifacts handler implementation that works the same as `MinimalCombinedArtifacts` but also
|
||||||
|
@ -739,6 +818,14 @@ impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
|
||||||
) -> Self::Artifact {
|
) -> Self::Artifact {
|
||||||
MinimalCombinedArtifacts::default().contract_to_artifact(file, name, contract, source_file)
|
MinimalCombinedArtifacts::default().contract_to_artifact(file, name, contract, source_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn standalone_source_file_to_artifact(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
file: &VersionedSourceFile,
|
||||||
|
) -> Option<Self::Artifact> {
|
||||||
|
MinimalCombinedArtifacts::default().standalone_source_file_to_artifact(path, file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1634,6 +1634,22 @@ pub struct SourceFile {
|
||||||
pub ast: Option<Ast>,
|
pub ast: Option<Ast>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === impl SourceFile ===
|
||||||
|
|
||||||
|
impl SourceFile {
|
||||||
|
/// Returns `true` if the source file contains at least 1 `ContractDefinition` such as
|
||||||
|
/// `contract`, `abstract contract`, `interface` or `library`
|
||||||
|
pub fn contains_contract_definition(&self) -> bool {
|
||||||
|
if let Some(ref ast) = self.ast {
|
||||||
|
// contract definitions are only allowed at the source-unit level <https://docs.soliditylang.org/en/latest/grammar.html>
|
||||||
|
return ast.nodes.iter().any(|node| node.node_type == NodeType::ContractDefinition)
|
||||||
|
// abstract contract, interfaces: ContractDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A wrapper type for a list of source files
|
/// A wrapper type for a list of source files
|
||||||
/// `path -> SourceFile`
|
/// `path -> SourceFile`
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
|
|
|
@ -265,23 +265,25 @@ impl<'a, T: ArtifactOutput> CompiledState<'a, T> {
|
||||||
fn write_artifacts(self) -> Result<ArtifactsState<'a, T>> {
|
fn write_artifacts(self) -> Result<ArtifactsState<'a, T>> {
|
||||||
let CompiledState { output, cache } = self;
|
let CompiledState { output, cache } = self;
|
||||||
|
|
||||||
// write all artifacts via the handler but only if the build succeeded
|
let project = cache.project();
|
||||||
let compiled_artifacts = if cache.project().no_artifacts {
|
// write all artifacts via the handler but only if the build succeeded and project wasn't
|
||||||
cache
|
// configured with `no_artifacts == true`
|
||||||
.project()
|
let compiled_artifacts = if project.no_artifacts {
|
||||||
.artifacts_handler()
|
project.artifacts_handler().output_to_artifacts(&output.contracts, &output.sources)
|
||||||
.output_to_artifacts(&output.contracts, &output.sources)
|
|
||||||
} else if output.has_error() {
|
} else if output.has_error() {
|
||||||
tracing::trace!("skip writing cache file due to solc errors: {:?}", output.errors);
|
tracing::trace!("skip writing cache file due to solc errors: {:?}", output.errors);
|
||||||
cache
|
project.artifacts_handler().output_to_artifacts(&output.contracts, &output.sources)
|
||||||
.project()
|
|
||||||
.artifacts_handler()
|
|
||||||
.output_to_artifacts(&output.contracts, &output.sources)
|
|
||||||
} else {
|
} else {
|
||||||
cache.project().artifacts_handler().on_output(
|
tracing::trace!(
|
||||||
|
"handling artifact output for {} contracts and {} sources",
|
||||||
|
output.contracts.len(),
|
||||||
|
output.sources.len()
|
||||||
|
);
|
||||||
|
// this emits the artifacts via the project's artifacts handler
|
||||||
|
project.artifacts_handler().on_output(
|
||||||
&output.contracts,
|
&output.contracts,
|
||||||
&output.sources,
|
&output.sources,
|
||||||
&cache.project().paths,
|
&project.paths,
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
contract::{CompactContract, CompactContractBytecode, Contract, ContractBytecode},
|
contract::{CompactContract, CompactContractBytecode, Contract, ContractBytecode},
|
||||||
CompactContractBytecodeCow, LosslessAbi, Offsets,
|
CompactContractBytecodeCow, LosslessAbi, Offsets,
|
||||||
},
|
},
|
||||||
ArtifactOutput, SourceFile,
|
ArtifactOutput, SourceFile, VersionedSourceFile,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{borrow::Cow, collections::btree_map::BTreeMap};
|
use std::{borrow::Cow, collections::btree_map::BTreeMap};
|
||||||
|
@ -135,6 +135,14 @@ impl ArtifactOutput for HardhatArtifacts {
|
||||||
deployed_link_references,
|
deployed_link_references,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn standalone_source_file_to_artifact(
|
||||||
|
&self,
|
||||||
|
_path: &str,
|
||||||
|
_file: &VersionedSourceFile,
|
||||||
|
) -> Option<Self::Artifact> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -36,7 +36,7 @@ use crate::{
|
||||||
artifacts::Sources,
|
artifacts::Sources,
|
||||||
cache::SolFilesCache,
|
cache::SolFilesCache,
|
||||||
error::{SolcError, SolcIoError},
|
error::{SolcError, SolcIoError},
|
||||||
sources::VersionedSourceFiles,
|
sources::{VersionedSourceFile, VersionedSourceFiles},
|
||||||
};
|
};
|
||||||
use artifacts::contract::Contract;
|
use artifacts::contract::Contract;
|
||||||
use compile::output::contracts::VersionedContracts;
|
use compile::output::contracts::VersionedContracts;
|
||||||
|
@ -830,6 +830,14 @@ impl<T: ArtifactOutput> ArtifactOutput for Project<T> {
|
||||||
) -> Artifacts<Self::Artifact> {
|
) -> Artifacts<Self::Artifact> {
|
||||||
self.artifacts_handler().output_to_artifacts(contracts, sources)
|
self.artifacts_handler().output_to_artifacts(contracts, sources)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn standalone_source_file_to_artifact(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
file: &VersionedSourceFile,
|
||||||
|
) -> Option<Self::Artifact> {
|
||||||
|
self.artifacts_handler().standalone_source_file_to_artifact(path, file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1304,6 +1304,160 @@ fn can_recompile_unchanged_with_empty_files() {
|
||||||
assert!(compiled.find("C").is_some());
|
assert!(compiled.find("C").is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_emit_empty_artifacts() {
|
||||||
|
let tmp = TempProject::dapptools().unwrap();
|
||||||
|
|
||||||
|
let top_level = tmp
|
||||||
|
.add_source(
|
||||||
|
"top_level",
|
||||||
|
r#"
|
||||||
|
function test() {}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
tmp.add_source(
|
||||||
|
"Contract",
|
||||||
|
r#"
|
||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
|
||||||
|
import "./top_level.sol";
|
||||||
|
|
||||||
|
contract Contract {
|
||||||
|
function a() public{
|
||||||
|
test();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
assert!(compiled.find("Contract").is_some());
|
||||||
|
assert!(compiled.find("top_level").is_some());
|
||||||
|
let mut artifacts = tmp.artifacts_snapshot().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(artifacts.artifacts.as_ref().len(), 2);
|
||||||
|
|
||||||
|
let mut top_level =
|
||||||
|
artifacts.artifacts.as_mut().remove(top_level.to_string_lossy().as_ref()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(top_level.len(), 1);
|
||||||
|
|
||||||
|
let artifact = top_level.remove("top_level").unwrap().remove(0);
|
||||||
|
assert!(artifact.artifact.ast.is_some());
|
||||||
|
|
||||||
|
// recompile
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(compiled.is_unchanged());
|
||||||
|
|
||||||
|
// modify standalone file
|
||||||
|
|
||||||
|
tmp.add_source(
|
||||||
|
"top_level",
|
||||||
|
r#"
|
||||||
|
error MyError();
|
||||||
|
function test() {}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
assert!(!compiled.is_unchanged());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_detect_contract_def_source_files() {
|
||||||
|
let tmp = TempProject::dapptools().unwrap();
|
||||||
|
|
||||||
|
let mylib = tmp
|
||||||
|
.add_source(
|
||||||
|
"MyLib",
|
||||||
|
r#"
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
library MyLib {
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let myinterface = tmp
|
||||||
|
.add_source(
|
||||||
|
"MyInterface",
|
||||||
|
r#"
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
interface MyInterface {}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mycontract = tmp
|
||||||
|
.add_source(
|
||||||
|
"MyContract",
|
||||||
|
r#"
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
contract MyContract {}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let myabstract_contract = tmp
|
||||||
|
.add_source(
|
||||||
|
"MyAbstractContract",
|
||||||
|
r#"
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
contract MyAbstractContract {}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let myerr = tmp
|
||||||
|
.add_source(
|
||||||
|
"MyError",
|
||||||
|
r#"
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
error MyError();
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let myfunc = tmp
|
||||||
|
.add_source(
|
||||||
|
"MyFunction",
|
||||||
|
r#"
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
function abc(){}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let compiled = tmp.compile().unwrap();
|
||||||
|
println!("{}", compiled);
|
||||||
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
|
||||||
|
let mut sources = compiled.output().sources;
|
||||||
|
let myfunc = sources.remove_by_path(myfunc.to_string_lossy()).unwrap();
|
||||||
|
assert!(!myfunc.contains_contract_definition());
|
||||||
|
|
||||||
|
let myerr = sources.remove_by_path(myerr.to_string_lossy()).unwrap();
|
||||||
|
assert!(!myerr.contains_contract_definition());
|
||||||
|
|
||||||
|
let mylib = sources.remove_by_path(mylib.to_string_lossy()).unwrap();
|
||||||
|
assert!(mylib.contains_contract_definition());
|
||||||
|
|
||||||
|
let myabstract_contract =
|
||||||
|
sources.remove_by_path(myabstract_contract.to_string_lossy()).unwrap();
|
||||||
|
assert!(myabstract_contract.contains_contract_definition());
|
||||||
|
|
||||||
|
let myinterface = sources.remove_by_path(myinterface.to_string_lossy()).unwrap();
|
||||||
|
assert!(myinterface.contains_contract_definition());
|
||||||
|
|
||||||
|
let mycontract = sources.remove_by_path(mycontract.to_string_lossy()).unwrap();
|
||||||
|
assert!(mycontract.contains_contract_definition());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_compile_sparse_with_link_references() {
|
fn can_compile_sparse_with_link_references() {
|
||||||
let tmp = TempProject::dapptools().unwrap();
|
let tmp = TempProject::dapptools().unwrap();
|
||||||
|
|
Loading…
Reference in New Issue