forge_verify/etherscan/
flatten.rs1use super::{EtherscanSourceProvider, VerifyArgs};
2use crate::provider::VerificationContext;
3use eyre::{Context, Result};
4use foundry_block_explorers::verify::CodeFormat;
5use foundry_compilers::{
6 AggregatedCompilerOutput,
7 artifacts::{BytecodeHash, Source, Sources},
8 buildinfo::RawBuildInfo,
9 compilers::{
10 Compiler, CompilerInput,
11 solc::{SolcCompiler, SolcLanguage, SolcVersionedInput},
12 },
13 solc::Solc,
14};
15use semver::{BuildMetadata, Version};
16use std::path::Path;
17
18#[derive(Debug)]
19pub struct EtherscanFlattenedSource;
20impl EtherscanSourceProvider for EtherscanFlattenedSource {
21 fn source(
22 &self,
23 args: &VerifyArgs,
24 context: &VerificationContext,
25 ) -> Result<(String, String, CodeFormat)> {
26 let metadata = context.project.settings.solc.metadata.as_ref();
27 let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default();
28
29 eyre::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 );
34
35 let source = context
36 .project
37 .paths
38 .clone()
39 .with_language::<SolcLanguage>()
40 .flatten(&context.target_path)
41 .wrap_err("Failed to flatten contract")?;
42
43 if !args.force {
44 self.check_flattened(source.clone(), &context.compiler_version, &context.target_path)
46 .map_err(|err| {
47 eyre::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 }
54
55 Ok((source, context.target_name.clone(), CodeFormat::SingleFile))
56 }
57}
58
59impl EtherscanFlattenedSource {
60 fn check_flattened(
75 &self,
76 content: impl Into<String>,
77 version: &Version,
78 contract_path: &Path,
79 ) -> Result<()> {
80 let version = strip_build_meta(version.clone());
81 let solc = Solc::find_or_install(&version)?;
82
83 let input = SolcVersionedInput::build(
84 Sources::from([("contract.sol".into(), Source::new(content))]),
85 Default::default(),
86 SolcLanguage::Solidity,
87 version.clone(),
88 );
89
90 let out = SolcCompiler::Specific(solc).compile(&input)?;
91 if out.errors.iter().any(|e| e.is_error()) {
92 let mut o = AggregatedCompilerOutput::<SolcCompiler>::default();
93 o.extend(version, RawBuildInfo::new(&input, &out, false)?, "default", out);
94 let diags = o.diagnostics(&[], &[], Default::default());
95
96 eyre::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 }
105
106 Ok(())
107 }
108}
109
110fn strip_build_meta(version: Version) -> Version {
116 if version.build != BuildMetadata::EMPTY {
117 Version::new(version.major, version.minor, version.patch)
118 } else {
119 version
120 }
121}