1use super::{EtherscanSourceProvider, VerifyArgs};
2use crate::provider::VerificationContext;
3use eyre::{Context, Result};
4use foundry_block_explorers::verify::CodeFormat;
5use foundry_compilers::{
6 artifacts::{BytecodeHash, Source, Sources},
7 buildinfo::RawBuildInfo,
8 compilers::{
9 solc::{SolcCompiler, SolcLanguage, SolcVersionedInput},
10 Compiler, CompilerInput,
11 },
12 solc::Solc,
13 AggregatedCompilerOutput,
14};
15use semver::{BuildMetadata, Version};
16use std::path::Path;
1718#[derive(Debug)]
19pub struct EtherscanFlattenedSource;
20impl EtherscanSourceProviderfor EtherscanFlattenedSource {
21fn source(
22&self,
23 args: &VerifyArgs,
24 context: &VerificationContext,
25 ) -> Result<(String, String, CodeFormat)> {
26let metadata = context.project.settings.solc.metadata.as_ref();
27let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default();
2829eyre::ensure!(
30 bch == BytecodeHash::Ipfs,
31"When using flattened source, bytecodeHash must be set to ipfs because Etherscan uses IPFS in its Compiler Settings when re-compiling your code. BytecodeHash is currently: {}. Hint: Set the bytecodeHash key in your foundry.toml :)",
32 bch,
33 );
3435let source = context36 .project
37 .paths
38 .clone()
39 .with_language::<SolcLanguage>()
40 .flatten(&context.target_path)
41 .wrap_err("Failed to flatten contract")?;
4243if !args.force {
44// solc dry run of flattened code
45self.check_flattened(source.clone(), &context.compiler_version, &context.target_path)
46 .map_err(|err| {
47eyre::eyre!(
48"Failed to compile the flattened code locally: `{}`\
49 To skip this solc dry, have a look at the `--force` flag of this command.",
50 err
51 )
52 })?;
53 }
5455Ok((source, context.target_name.clone(), CodeFormat::SingleFile))
56 }
57}
5859impl EtherscanFlattenedSource {
60/// Attempts to compile the flattened content locally with the compiler version.
61 ///
62 /// This expects the completely flattened content and will try to compile it using the
63 /// provided compiler. If the compiler is missing it will be installed.
64 ///
65 /// # Errors
66 ///
67 /// If it failed to install a missing solc compiler
68 ///
69 /// # Exits
70 ///
71 /// If the solc compiler output contains errors, this could either be due to a bug in the
72 /// flattening code or could to conflict in the flattened code, for example if there are
73 /// multiple interfaces with the same name.
74fn check_flattened(
75&self,
76 content: impl Into<String>,
77 version: &Version,
78 contract_path: &Path,
79 ) -> Result<()> {
80let version = strip_build_meta(version.clone());
81let solc = Solc::find_or_install(&version)?;
8283let input = SolcVersionedInput::build(
84Sources::from([("contract.sol".into(), Source::new(content))]),
85Default::default(),
86 SolcLanguage::Solidity,
87version.clone(),
88 );
8990let out = SolcCompiler::Specific(solc).compile(&input)?;
91if out.errors.iter().any(|e| e.is_error()) {
92let mut o = AggregatedCompilerOutput::<SolcCompiler>::default();
93o.extend(version, RawBuildInfo::new(&input, &out, false)?, "default", out);
94let diags = o.diagnostics(&[], &[], Default::default());
9596eyre::bail!(
97"\
98Failed to compile the flattened code locally.
99This could be a bug, please inspect the output of `forge flatten {}` and report an issue.
100To skip this solc dry, pass `--force`.
101Diagnostics: {diags}",
102 contract_path.display()
103 );
104 }
105106Ok(())
107 }
108}
109110/// Strips [BuildMetadata] from the [Version]
111///
112/// **Note:** this is only for local compilation as a dry run, therefore this will return a
113/// sanitized variant of the specific version so that it can be installed. This is merely
114/// intended to ensure the flattened code can be compiled without errors.
115fn strip_build_meta(version: Version) -> Version {
116if version.build != BuildMetadata::EMPTY {
117Version::new(version.major, version.minor, version.patch)
118 } else {
119version120 }
121}