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(script): support custom create2 deployer #9278

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,10 @@ impl InspectorExt for Cheatcodes {
false
}
}

fn create2_deployer(&self) -> Address {
self.config.evm_opts.create2_deployer
}
}

impl Cheatcodes {
Expand Down
8 changes: 8 additions & 0 deletions crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,9 @@ pub struct Config {
/// CREATE2 salt to use for the library deployment in scripts.
pub create2_library_salt: B256,

/// The CREATE2 deployer address to use.
pub create2_deployer: Address,

/// Configuration for Vyper compiler
pub vyper: VyperConfig,

Expand Down Expand Up @@ -539,6 +542,10 @@ impl Config {
/// Default salt for create2 library deployments
pub const DEFAULT_CREATE2_LIBRARY_SALT: FixedBytes<32> = FixedBytes::<32>::ZERO;

/// Default create2 deployer
pub const DEFAULT_CREATE2_DEPLOYER: Address =
address!("4e59b44847b379578588920ca78fbf26c0b4956c");

/// Docker image with eof-enabled solc binary
pub const EOF_SOLC_IMAGE: &'static str = "ghcr.io/paradigmxyz/forge-eof@sha256:46f868ce5264e1190881a3a335d41d7f42d6f26ed20b0c823609c715e38d603f";

Expand Down Expand Up @@ -2233,6 +2240,7 @@ impl Default for Config {
labels: Default::default(),
unchecked_cheatcode_artifacts: false,
create2_library_salt: Self::DEFAULT_CREATE2_LIBRARY_SALT,
create2_deployer: Self::DEFAULT_CREATE2_DEPLOYER,
skip: vec![],
dependencies: Default::default(),
soldeer: Default::default(),
Expand Down
7 changes: 7 additions & 0 deletions crates/evm/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

use crate::constants::DEFAULT_CREATE2_DEPLOYER;
use alloy_primitives::Address;
use auto_impl::auto_impl;
use backend::DatabaseExt;
use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, EvmContext, Inspector};
Expand Down Expand Up @@ -54,6 +56,11 @@ pub trait InspectorExt: for<'a> Inspector<&'a mut dyn DatabaseExt> {
fn is_alphanet(&self) -> bool {
false
}

/// Returns the CREATE2 deployer address.
fn create2_deployer(&self) -> Address {
DEFAULT_CREATE2_DEPLOYER
}
}

impl InspectorExt for NoOpInspector {}
Expand Down
32 changes: 30 additions & 2 deletions crates/evm/core/src/opts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::fork::environment;
use crate::fork::CreateFork;
use crate::{constants::DEFAULT_CREATE2_DEPLOYER, fork::CreateFork};
use alloy_primitives::{Address, B256, U256};
use alloy_provider::Provider;
use alloy_rpc_types::AnyNetworkBlock;
Expand All @@ -9,7 +9,7 @@ use foundry_config::{Chain, Config};
use revm::primitives::{BlockEnv, CfgEnv, TxEnv};
use serde::{Deserialize, Deserializer, Serialize};

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EvmOpts {
/// The EVM environment configuration.
#[serde(flatten)]
Expand Down Expand Up @@ -66,6 +66,34 @@ pub struct EvmOpts {

/// whether to enable Alphanet features.
pub alphanet: bool,

/// The CREATE2 deployer's address.
pub create2_deployer: Address,
}

impl Default for EvmOpts {
fn default() -> Self {
Self {
env: Env::default(),
fork_url: None,
fork_block_number: None,
fork_retries: None,
fork_retry_backoff: None,
compute_units_per_second: None,
no_rpc_rate_limit: false,
no_storage_caching: false,
initial_balance: U256::default(),
sender: Address::default(),
ffi: false,
always_use_create_2_factory: false,
verbosity: 0,
memory_limit: 0,
isolate: false,
disable_block_gas_limit: false,
alphanet: false,
create2_deployer: DEFAULT_CREATE2_DEPLOYER,
}
}
}

impl EvmOpts {
Expand Down
23 changes: 13 additions & 10 deletions crates/evm/core/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
pub use crate::ic::*;
use crate::{
backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER, precompiles::ALPHANET_P256,
InspectorExt,
};
use crate::{backend::DatabaseExt, precompiles::ALPHANET_P256, InspectorExt};
use alloy_json_abi::{Function, JsonAbi};
use alloy_primitives::{Address, Selector, TxKind, U256};
use alloy_provider::{
Expand Down Expand Up @@ -148,12 +145,16 @@ pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 {
spent - (refunded).min(spent / refund_quotient)
}

fn get_create2_factory_call_inputs(salt: U256, inputs: CreateInputs) -> CallInputs {
fn get_create2_factory_call_inputs(
salt: U256,
inputs: CreateInputs,
deployer: Address,
) -> CallInputs {
let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat();
CallInputs {
caller: inputs.caller,
bytecode_address: DEFAULT_CREATE2_DEPLOYER,
target_address: DEFAULT_CREATE2_DEPLOYER,
bytecode_address: deployer,
target_address: deployer,
scheme: CallScheme::Call,
value: CallValue::Transfer(inputs.value),
input: calldata.into(),
Expand All @@ -164,7 +165,7 @@ fn get_create2_factory_call_inputs(salt: U256, inputs: CreateInputs) -> CallInpu
}
}

/// Used for routing certain CREATE2 invocations through [DEFAULT_CREATE2_DEPLOYER].
/// Used for routing certain CREATE2 invocations through CREATE2_DEPLOYER.
///
/// Overrides create hook with CALL frame if [InspectorExt::should_use_create2_factory] returns
/// true. Keeps track of overridden frames and handles outcome in the overridden insert_call_outcome
Expand All @@ -189,8 +190,10 @@ pub fn create2_handler_register<I: InspectorExt>(

let gas_limit = inputs.gas_limit;

// Get CREATE2 deployer.
let create2_deployer = ctx.external.create2_deployer();
// Generate call inputs for CREATE2 factory.
let mut call_inputs = get_create2_factory_call_inputs(salt, *inputs);
let mut call_inputs = get_create2_factory_call_inputs(salt, *inputs, create2_deployer);

// Call inspector to change input or return outcome.
let outcome = ctx.external.call(&mut ctx.evm, &mut call_inputs);
Expand All @@ -201,7 +204,7 @@ pub fn create2_handler_register<I: InspectorExt>(
.push((ctx.evm.journaled_state.depth(), call_inputs.clone()));

// Sanity check that CREATE2 deployer exists.
let code_hash = ctx.evm.load_account(DEFAULT_CREATE2_DEPLOYER)?.info.code_hash;
let code_hash = ctx.evm.load_account(create2_deployer)?.info.code_hash;
if code_hash == KECCAK_EMPTY {
return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome {
result: InterpreterResult {
Expand Down
11 changes: 11 additions & 0 deletions crates/evm/evm/src/inspectors/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ pub struct InspectorStackInner {
pub tracer: Option<TracingInspector>,
pub enable_isolation: bool,
pub alphanet: bool,
pub create2_deployer: Address,

/// Flag marking if we are in the inner EVM context.
pub in_inner_context: bool,
Expand Down Expand Up @@ -398,6 +399,12 @@ impl InspectorStack {
self.alphanet = yes;
}

/// Set the CREATE2 deployer address.
#[inline]
pub fn set_create2_deployer(&mut self, deployer: Address) {
self.create2_deployer = deployer;
}

/// Set whether to enable the log collector.
#[inline]
pub fn collect_logs(&mut self, yes: bool) {
Expand Down Expand Up @@ -1124,6 +1131,10 @@ impl InspectorExt for InspectorStack {
fn is_alphanet(&self) -> bool {
self.alphanet
}

fn create2_deployer(&self) -> Address {
self.create2_deployer
}
}

impl<'a> Deref for InspectorStackRefMut<'a> {
Expand Down
1 change: 1 addition & 0 deletions crates/forge/tests/cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ forgetest!(can_extract_config_values, |prj, cmd| {
isolate: true,
unchecked_cheatcode_artifacts: false,
create2_library_salt: Config::DEFAULT_CREATE2_LIBRARY_SALT,
create2_deployer: Config::DEFAULT_CREATE2_DEPLOYER,
vyper: Default::default(),
skip: vec![],
dependencies: Default::default(),
Expand Down
7 changes: 4 additions & 3 deletions crates/script/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use foundry_compilers::{
utils::source_files_iter,
ArtifactId, ProjectCompileOutput,
};
use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::debug::ContractSources};
use foundry_evm::traces::debug::ContractSources;
use foundry_linking::Linker;
use std::{path::PathBuf, str::FromStr, sync::Arc};

Expand All @@ -40,9 +40,10 @@ impl BuildData {
/// Links contracts. Uses CREATE2 linking when possible, otherwise falls back to
/// default linking with sender nonce and address.
pub async fn link(self, script_config: &ScriptConfig) -> Result<LinkedBuildData> {
let create2_deployer = script_config.evm_opts.create2_deployer;
let can_use_create2 = if let Some(fork_url) = &script_config.evm_opts.fork_url {
let provider = try_get_http_provider(fork_url)?;
let deployer_code = provider.get_code_at(DEFAULT_CREATE2_DEPLOYER).await?;
let deployer_code = provider.get_code_at(create2_deployer).await?;

!deployer_code.is_empty()
} else {
Expand All @@ -57,7 +58,7 @@ impl BuildData {
self.get_linker()
.link_with_create2(
known_libraries.clone(),
DEFAULT_CREATE2_DEPLOYER,
create2_deployer,
script_config.config.create2_library_salt,
&self.target,
)
Expand Down
20 changes: 16 additions & 4 deletions crates/script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ use foundry_config::{
};
use foundry_evm::{
backend::Backend,
constants::DEFAULT_CREATE2_DEPLOYER,
executors::ExecutorBuilder,
inspectors::{
cheatcodes::{BroadcastableTransactions, Wallets},
Expand Down Expand Up @@ -192,6 +191,10 @@ pub struct ScriptArgs {
)]
pub with_gas_price: Option<U256>,

/// The CREATE2 deployer address to use, this will override the one in the config.
#[arg(long, value_name = "ADDRESS")]
pub create2_deployer: Option<Address>,

/// Timeout to use for broadcasting transactions.
#[arg(long, env = "ETH_TIMEOUT")]
pub timeout: Option<u64>,
Expand Down Expand Up @@ -223,6 +226,10 @@ impl ScriptArgs {
evm_opts.sender = sender;
}

if let Some(create2_deployer) = self.create2_deployer {
evm_opts.create2_deployer = create2_deployer;
}

let script_config = ScriptConfig::new(config, evm_opts).await?;

Ok(PreprocessedState { args: self, script_config, script_wallets })
Expand All @@ -232,7 +239,9 @@ impl ScriptArgs {
pub async fn run_script(self) -> Result<()> {
trace!(target: "script", "executing script command");

let compiled = self.preprocess().await?.compile()?;
let state = self.preprocess().await?;
let create2_deployer = state.script_config.evm_opts.create2_deployer;
let compiled = state.compile()?;

// Move from `CompiledState` to `BundledState` either by resuming or executing and
// simulating script.
Expand Down Expand Up @@ -284,9 +293,10 @@ impl ScriptArgs {
pre_simulation.args.check_contract_sizes(
&pre_simulation.execution_result,
&pre_simulation.build_data.known_contracts,
create2_deployer,
)?;

pre_simulation.fill_metadata().await?.bundle().await?
pre_simulation.fill_metadata(create2_deployer).await?.bundle().await?
};

// Exit early in case user didn't provide any broadcast/verify related flags.
Expand Down Expand Up @@ -372,6 +382,7 @@ impl ScriptArgs {
&self,
result: &ScriptResult,
known_contracts: &ContractsByArtifact,
create2_deployer: Address,
) -> Result<()> {
// (name, &init, &deployed)[]
let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![];
Expand Down Expand Up @@ -416,7 +427,7 @@ impl ScriptArgs {

// Find if it's a CREATE or CREATE2. Otherwise, skip transaction.
if let Some(TxKind::Call(to)) = to {
if to == DEFAULT_CREATE2_DEPLOYER {
if to == create2_deployer {
// Size of the salt prefix.
offset = 32;
} else {
Expand Down Expand Up @@ -541,6 +552,7 @@ impl ScriptConfig {
// dapptools compatibility
1
};

Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default() })
}

Expand Down
13 changes: 8 additions & 5 deletions crates/script/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use eyre::Result;
use foundry_cheatcodes::BroadcastableTransaction;
use foundry_config::Config;
use foundry_evm::{
constants::{CALLER, DEFAULT_CREATE2_DEPLOYER},
constants::CALLER,
executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult},
opts::EvmOpts,
revm::interpreter::{return_ok, InstructionResult},
Expand Down Expand Up @@ -38,6 +38,9 @@ impl ScriptRunner {
) -> Result<(Address, ScriptResult)> {
trace!(target: "script", "executing setUP()");

// set CREATE2 deployer from EvmOpts.
self.executor.inspector_mut().set_create2_deployer(self.evm_opts.create2_deployer);

if !is_broadcast {
if self.evm_opts.sender == Config::DEFAULT_SENDER {
// We max out their balance so that they can deploy and make calls.
Expand Down Expand Up @@ -82,9 +85,9 @@ impl ScriptRunner {
})
}),
ScriptPredeployLibraries::Create2(libraries, salt) => {
let create2_deployer = self.evm_opts.create2_deployer;
for library in libraries {
let address =
DEFAULT_CREATE2_DEPLOYER.create2_from_code(salt, library.as_ref());
let address = create2_deployer.create2_from_code(salt, library.as_ref());
// Skip if already deployed
if !self.executor.is_empty_code(address)? {
continue;
Expand All @@ -94,7 +97,7 @@ impl ScriptRunner {
.executor
.transact_raw(
self.evm_opts.sender,
DEFAULT_CREATE2_DEPLOYER,
create2_deployer,
calldata.clone().into(),
U256::from(0),
)
Expand All @@ -110,7 +113,7 @@ impl ScriptRunner {
from: Some(self.evm_opts.sender),
input: calldata.into(),
nonce: Some(sender_nonce + library_transactions.len() as u64),
to: Some(TxKind::Call(DEFAULT_CREATE2_DEPLOYER)),
to: Some(TxKind::Call(create2_deployer)),
..Default::default()
}
.into(),
Expand Down
8 changes: 6 additions & 2 deletions crates/script/src/simulate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl PreSimulationState {
/// left empty.
///
/// Both modes will panic if any of the transactions have None for the `rpc` field.
pub async fn fill_metadata(self) -> Result<FilledTransactionsState> {
pub async fn fill_metadata(self, create2_deployer: Address) -> Result<FilledTransactionsState> {
let address_to_abi = self.build_address_to_abi_map();

let mut transactions = self
Expand All @@ -64,7 +64,11 @@ impl PreSimulationState {
let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc);

if let Some(TxKind::Call(_)) = to {
builder.set_call(&address_to_abi, &self.execution_artifacts.decoder)?;
builder.set_call(
&address_to_abi,
&self.execution_artifacts.decoder,
create2_deployer,
)?;
} else {
builder.set_create(false, sender.create(nonce), &address_to_abi)?;
}
Expand Down
Loading
Loading