Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(forge): verify broadcasted contracts on forge script #1778

Merged
merged 7 commits into from Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/src/cmd/forge/debug.rs
Expand Up @@ -59,6 +59,8 @@ impl DebugArgs {
resume: false,
debug: true,
slow: false,
etherscan_api_key: None,
verify: false,
};
script.run_script().await
}
Expand Down
17 changes: 10 additions & 7 deletions cli/src/cmd/forge/script/broadcast.rs
@@ -1,8 +1,5 @@
use crate::{
cmd::{
forge::script::receipts::{wait_for_pending, wait_for_receipts},
ScriptSequence,
},
cmd::{forge::script::receipts::wait_for_receipts, ScriptSequence, VerifyBundle},
opts::WalletType,
utils::get_http_provider,
};
Expand All @@ -23,8 +20,6 @@ impl ScriptArgs {
) -> eyre::Result<()> {
let provider = get_http_provider(fork_url);

wait_for_pending(&provider, deployment_sequence).await?;

if deployment_sequence.receipts.len() < deployment_sequence.transactions.len() {
let required_addresses = deployment_sequence
.transactions
Expand Down Expand Up @@ -117,10 +112,11 @@ impl ScriptArgs {
transactions: Option<VecDeque<TypedTransaction>>,
decoder: &mut CallTraceDecoder,
script_config: &ScriptConfig,
verify: VerifyBundle,
) -> eyre::Result<()> {
if let Some(txs) = transactions {
if script_config.evm_opts.fork_url.is_some() {
let gas_filled_txs =
let (gas_filled_txs, create2_contracts) =
self.execute_transactions(txs, script_config, decoder)
.await
.map_err(|_| eyre::eyre!("One or more transactions failed when simulating the on-chain version. Check the trace by re-running with `-vvv`"))?;
Expand All @@ -147,8 +143,15 @@ impl ScriptArgs {
let mut deployment_sequence =
ScriptSequence::new(txes, &self.sig, target, &script_config.config, chain)?;

create2_contracts
.into_iter()
.for_each(|addr| deployment_sequence.add_create2(addr));

if self.broadcast {
self.send_transactions(&mut deployment_sequence, &fork_url).await?;
if self.verify {
deployment_sequence.verify_contracts(verify, chain).await?;
}
} else {
println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.");
}
Expand Down
3 changes: 1 addition & 2 deletions cli/src/cmd/forge/script/build.rs
Expand Up @@ -148,7 +148,7 @@ impl ScriptArgs {
&mut self,
script_config: &ScriptConfig,
) -> eyre::Result<(Project, ProjectCompileOutput)> {
let project = script_config.config.ephemeral_no_artifacts_project()?;
let project = script_config.config.project()?;

let output = match dunce::canonicalize(&self.path) {
// We got passed an existing path to the contract
Expand All @@ -162,7 +162,6 @@ impl ScriptArgs {

(path, output)
} else {
let project = script_config.config.project()?;
let output = compile::compile(&project, false, false)?;
let cache = SolFilesCache::read_joined(&project.paths)?;

Expand Down
40 changes: 30 additions & 10 deletions cli/src/cmd/forge/script/cmd.rs
@@ -1,9 +1,9 @@
use crate::{
cmd::{unwrap_contracts, ScriptSequence},
cmd::{unwrap_contracts, ScriptSequence, VerifyBundle},
utils::get_http_provider,
};

use ethers::{
abi::Abi,
prelude::{artifacts::CompactContractBytecode, ArtifactId, Middleware, Signer},
types::{transaction::eip2718::TypedTransaction, U256},
};
Expand Down Expand Up @@ -39,13 +39,18 @@ impl ScriptArgs {
project,
target,
contract,
highlevel_known_contracts,
mut highlevel_known_contracts,
predeploy_libraries,
known_contracts: default_known_contracts,
sources,
} = self.build(&script_config)?;

if self.resume {
let mut verify = VerifyBundle::new(
&script_config.config,
unwrap_contracts(&highlevel_known_contracts, false),
);

if self.resume || (self.verify && !self.broadcast) {
let fork_url = self
.evm_opts
.fork_url
Expand All @@ -58,9 +63,18 @@ impl ScriptArgs {

let mut deployment_sequence =
ScriptSequence::load(&script_config.config, &self.sig, &target, chain)?;
self.send_transactions(&mut deployment_sequence, &fork_url).await?;

receipts::wait_for_pending(&provider, &mut deployment_sequence).await?;

if self.resume {
self.send_transactions(&mut deployment_sequence, &fork_url).await?;
}

if self.verify {
deployment_sequence.verify_contracts(verify, chain).await?;
}
} else {
let mut known_contracts = unwrap_contracts(&highlevel_known_contracts);
let known_contracts = unwrap_contracts(&highlevel_known_contracts, true);

// execute once with default sender
let sender = script_config.evm_opts.sender;
Expand All @@ -77,7 +91,7 @@ impl ScriptArgs {
result.transactions.as_ref(),
&predeploy_libraries,
)? {
known_contracts = self
highlevel_known_contracts = self
.rerun_with_new_deployer(
project,
&mut script_config,
Expand All @@ -87,7 +101,11 @@ impl ScriptArgs {
)
.await?;
// redo traces
decoder = self.decode_traces(&script_config, &mut result, &known_contracts)?;
decoder = self.decode_traces(
&script_config,
&mut result,
&unwrap_contracts(&highlevel_known_contracts, true),
)?;
} else {
// prepend predeploy libraries
let mut lib_deploy = self.create_deploy_transactions(
Expand All @@ -106,11 +124,13 @@ impl ScriptArgs {

self.show_traces(&script_config, &decoder, &mut result)?;

verify.known_contracts = unwrap_contracts(&highlevel_known_contracts, false);
self.handle_broadcastable_transactions(
&target,
result.transactions,
&mut decoder,
&script_config,
verify,
)
.await?;
}
Expand All @@ -127,7 +147,7 @@ impl ScriptArgs {
new_sender: Address,
first_run_result: &mut ScriptResult,
default_known_contracts: BTreeMap<ArtifactId, CompactContractBytecode>,
) -> eyre::Result<BTreeMap<ArtifactId, (Abi, Vec<u8>)>> {
) -> eyre::Result<BTreeMap<ArtifactId, ContractBytecodeSome>> {
// if we had a new sender that requires relinking, we need to
// get the nonce mainnet for accurate addresses for predeploy libs
let nonce = foundry_utils::next_nonce(
Expand Down Expand Up @@ -165,7 +185,7 @@ impl ScriptArgs {
*first_run_result = result;
first_run_result.transactions = Some(txs);

Ok(unwrap_contracts(&highlevel_known_contracts))
Ok(highlevel_known_contracts)
}

/// In case the user has loaded *only* one private-key, we can assume that he's using it as the
Expand Down
24 changes: 18 additions & 6 deletions cli/src/cmd/forge/script/executor.rs
@@ -1,6 +1,8 @@
use crate::{cmd::needs_setup, utils};

use cast::executor::inspector::DEFAULT_CREATE2_DEPLOYER;
use ethers::{
prelude::NameOrAddress,
solc::artifacts::CompactContractBytecode,
types::{transaction::eip2718::TypedTransaction, Address, U256},
};
Expand Down Expand Up @@ -64,18 +66,20 @@ impl ScriptArgs {
Ok(result)
}

/// Executes a list of transactions locally and persists their state.
/// Executes a list of transactions locally and persists their state. Returns the transactions
/// and any CREATE2 contract addresses created.
pub async fn execute_transactions(
&self,
transactions: VecDeque<TypedTransaction>,
script_config: &ScriptConfig,
decoder: &mut CallTraceDecoder,
) -> eyre::Result<VecDeque<TypedTransaction>> {
) -> eyre::Result<(VecDeque<TypedTransaction>, Vec<Address>)> {
let mut runner = self.prepare_runner(script_config, script_config.evm_opts.sender).await;

let mut failed = false;
let mut sum_gas = 0;
let mut final_txs = transactions.clone();
let mut create2_contracts = vec![];

if script_config.evm_opts.verbosity > 3 {
println!("==========================");
Expand All @@ -101,9 +105,17 @@ impl ScriptArgs {
.enumerate()
.for_each(|(i, mut result)| {
match &mut final_txs[i] {
// We inflate the gas used by the transaction by x1.3 since the estimation might
// be off
TypedTransaction::Legacy(tx) => tx.gas = Some(U256::from(result.gas * 13 / 10)),
TypedTransaction::Legacy(tx) => {
// We store the CREATE2 address, since it's hard to get it otherwise
if let Some(NameOrAddress::Address(to)) = tx.to {
if to == DEFAULT_CREATE2_DEPLOYER {
create2_contracts.push(Address::from_slice(&result.returned));
}
}
// We inflate the gas used by the transaction by x1.3 since the estimation
// might be off
tx.gas = Some(U256::from(result.gas * 13 / 10))
}
_ => unreachable!(),
}

Expand All @@ -126,7 +138,7 @@ impl ScriptArgs {
println!("\n==========================\n");
println!("Estimated total gas used for script: {}", sum_gas);
println!("==========================");
Ok(final_txs)
Ok((final_txs, create2_contracts))
}
}

Expand Down
10 changes: 10 additions & 0 deletions cli/src/cmd/forge/script/mod.rs
Expand Up @@ -101,6 +101,16 @@ pub struct ScriptArgs {
help = "Makes sure a transaction is sent, only after its previous one has been confirmed and succeeded."
)]
pub slow: bool,

#[clap(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")]
pub etherscan_api_key: Option<String>,

#[clap(
long,
help = "If it finds a matching broadcast log, it tries to verify every contract found in the receipts.",
requires = "etherscan-api-key"
)]
pub verify: bool,
}

pub struct ScriptResult {
Expand Down
5 changes: 2 additions & 3 deletions cli/src/cmd/forge/script/receipts.rs
Expand Up @@ -76,9 +76,8 @@ pub async fn wait_for_receipts(
};
}

// Receipts may have arrived out of order
receipts.sort_by(|a, b| a.1.cmp(&b.1));
for (receipt, _) in receipts {
for (receipt, nonce) in receipts {
print_receipt(&receipt, nonce)?;
deployment_sequence.add_receipt(receipt);
}

Expand Down
5 changes: 4 additions & 1 deletion cli/src/cmd/forge/verify.rs
Expand Up @@ -114,13 +114,14 @@ impl VerifyArgs {
let retry: Retry = self.retry.into();
let resp = retry.run_async(|| {
async {
println!("\nSubmitting verification for [{}] {:?}.", verify_args.contract_name, verify_args.address);
let resp = etherscan
.submit_contract_verification(&verify_args)
.await
.wrap_err("Failed to submit contract verification")?;

if resp.status == "0" {
if resp.message == "Contract source code already verified" {
if resp.result == "Contract source code already verified" {
return Ok(None)
}

Expand Down Expand Up @@ -159,6 +160,8 @@ impl VerifyArgs {
};
return check_args.run().await
}
} else {
println!("Contract source code already verified");
}

Ok(())
Expand Down