forge_verify/etherscan/
flatten.rs1use super::{EtherscanSourceProvider, VerifyArgs};
2use crate::provider::VerificationContext;
3use eyre::Result;
4use foundry_block_explorers::verify::CodeFormat;
5use foundry_common::flatten;
6use foundry_compilers::{
7 AggregatedCompilerOutput,
8 artifacts::{BytecodeHash, Source, Sources},
9 buildinfo::RawBuildInfo,
10 compilers::{
11 Compiler, CompilerInput,
12 solc::{SolcCompiler, SolcLanguage, SolcVersionedInput},
13 },
14 solc::Solc,
15};
16use semver::{BuildMetadata, Version};
17use std::path::Path;
18
19#[derive(Debug)]
20pub struct EtherscanFlattenedSource;
21impl EtherscanSourceProvider for EtherscanFlattenedSource {
22 fn source(
23 &self,
24 args: &VerifyArgs,
25 context: &VerificationContext,
26 ) -> Result<(String, String, CodeFormat)> {
27 let metadata = context.project.settings.solc.metadata.as_ref();
28 let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default();
29
30 eyre::ensure!(
31 bch == BytecodeHash::Ipfs,
32 "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 :)",
33 bch,
34 );
35
36 let flattened_source = flatten(context.project.clone(), &context.target_path)?;
37 if !args.force {
38 self.check_flattened(
40 flattened_source.clone(),
41 &context.compiler_version,
42 &context.target_path,
43 )
44 .map_err(|err| {
45 eyre::eyre!(
46 "Failed to compile the flattened code locally: `{}`\
47 To skip this solc dry, have a look at the `--force` flag of this command.",
48 err
49 )
50 })?;
51 }
52
53 Ok((flattened_source, context.target_name.clone(), CodeFormat::SingleFile))
54 }
55}
56
57impl EtherscanFlattenedSource {
58 fn check_flattened(
73 &self,
74 content: impl Into<String>,
75 version: &Version,
76 contract_path: &Path,
77 ) -> Result<()> {
78 let version = strip_build_meta(version.clone());
79 let solc = Solc::find_or_install(&version)?;
80
81 let input = SolcVersionedInput::build(
82 Sources::from([("contract.sol".into(), Source::new(content))]),
83 Default::default(),
84 SolcLanguage::Solidity,
85 version.clone(),
86 );
87
88 let out = SolcCompiler::Specific(solc).compile(&input)?;
89 if out.errors.iter().any(|e| e.is_error()) {
90 let mut o = AggregatedCompilerOutput::<SolcCompiler>::default();
91 o.extend(version, RawBuildInfo::new(&input, &out, false)?, "default", out);
92 let diags = o.diagnostics(&[], &[], Default::default());
93
94 eyre::bail!(
95 "\
96Failed to compile the flattened code locally.
97This could be a bug, please inspect the output of `forge flatten {}` and report an issue.
98To skip this solc dry, pass `--force`.
99Diagnostics: {diags}",
100 contract_path.display()
101 );
102 }
103
104 Ok(())
105 }
106}
107
108fn strip_build_meta(version: Version) -> Version {
114 if version.build != BuildMetadata::EMPTY {
115 Version::new(version.major, version.minor, version.patch)
116 } else {
117 version
118 }
119}