Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ compilation_restrictions = [
{ paths = "src/pkgs/v3-periphery/contracts/NonfungiblePositionManager.sol", optimizer_runs = 2000 },
{ paths = "src/pkgs/v3-periphery/contracts/NonfungibleTokenPositionDescriptor.sol", optimizer_runs = 1000 },
{ paths = "src/pkgs/v3-periphery/contracts/libraries/NFTDescriptor.sol", optimizer_runs = 1000 },
{ paths = "src/pkgs/v3-periphery/contracts/*.sol", version = "0.7.6", via_ir = false, max_optimizer_runs = 1000000 },
{ paths = "src/pkgs/v3-periphery/contracts/SwapRouter.sol", version = "0.7.6", via_ir = false, min_optimizer_runs = 1000000 },
{ paths = "src/pkgs/v3-periphery/contracts/V3Migrator.sol", version = "0.7.6", via_ir = false, min_optimizer_runs = 1000000 },
{ paths = "src/pkgs/v3-periphery/**/libraries/**", version = "<0.8.0" },
# permit2
{ paths = "src/pkgs/permit2/src/**", version = "0.8.17", via_ir = true },
Expand Down
2 changes: 1 addition & 1 deletion script/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
src/assets/chains.json
src/assets/
2 changes: 1 addition & 1 deletion script/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ regex = "1.11.0"
tokio = { version = "1.40.0", features = ["full"] }
alloy = { version = "0.4.2", features = ["default", "json-abi", "transports", "providers", "dyn-abi", "rpc-types-trace", "rpc-types-debug"] }
eyre = "0.6.12"
reqwest = "0.12.8"
reqwest = { version = "0.12.8", features = ["json", "blocking"] }
openssl = { version = "0.10.35", features = ["vendored"] }

[build-dependencies]
Expand Down
47 changes: 35 additions & 12 deletions script/cli/build.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
use reqwest;
use std::fs;
use std::{env, fs};

fn main() {
// Create assets directory
let clean = env::var("CLEAN").unwrap_or("false".to_string()) == "true";
fs::create_dir_all("./src/assets").expect("Failed to create assets directory");
if clean {
// Download chains.json using reqwest
let response = reqwest::blocking::get("https://chainid.network/chains.json")
.expect("Failed to download chains.json");

// Download chains.json using reqwest
let response = reqwest::blocking::get("https://chainid.network/chains.json")
.expect("Failed to download chains.json");
if !response.status().is_success() {
panic!("Failed to download chains.json: HTTP {}", response.status());
}

if !response.status().is_success() {
panic!("Failed to download chains.json: HTTP {}", response.status());
}
let content = response
.bytes()
.expect("Failed to read chains response body");
fs::write("./src/assets/chains.json", content).expect("Failed to write chains.json");

// Tell cargo to re-run this if chains.json changes
println!("cargo:rerun-if-changed=src/assets/chains.json");

// Download etherscan_chainlist.json using reqwest
let response = reqwest::blocking::get("https://api.etherscan.io/v2/chainlist")
.expect("Failed to download etherscan_chainlist.json");

let content = response.bytes().expect("Failed to read response body");
fs::write("./src/assets/chains.json", content).expect("Failed to write chains.json");
if !response.status().is_success() {
panic!(
"Failed to download etherscan_chainlist.json: HTTP {}",
response.status()
);
}

// Tell cargo to re-run this if chains.json changes
println!("cargo:rerun-if-changed=src/assets/chains.json");
let content = response
.bytes()
.expect("Failed to read etherscan_chainlist response body");
fs::write("./src/assets/etherscan_chainlist.json", content)
.expect("Failed to write etherscan_chainlist.json");

// Tell cargo to re-run this if etherscan_chainlist.json changes
println!("cargo:rerun-if-changed=src/assets/etherscan_chainlist.json");
}
}
6 changes: 3 additions & 3 deletions script/cli/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ default:

# Build the project and copy the binary to the project root
build:
cargo build --release
CLEAN=true cargo build --release
cp ./target/release/deploy-cli ../../deploy-cli

# Run tests
Expand All @@ -25,12 +25,12 @@ clean:

# Build and run the project
run:
cargo run -- --dir ../..
CLEAN=true cargo run -- --dir ../..

# Watch the project and run it when the code changes
watch:
cargo watch -x 'run -- --dir ../..'

# Install the project
install:
cargo install --path .
CLEAN=true cargo install --path .
150 changes: 81 additions & 69 deletions script/cli/src/libs/explorer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::util::chain_config::Explorer;
use crate::{errors::log, state_manager::STATE_MANAGER};
use alloy::{
json_abi::{Constructor, JsonAbi},
Expand All @@ -8,102 +7,98 @@ use alloy::{
#[derive(Clone, PartialEq, Eq, Default)]
pub enum SupportedExplorerType {
#[default]
Manual,
EtherscanV2,
Etherscan,
Blockscout,
}

#[derive(Default, Clone)]
pub struct ExplorerApiLib {
pub struct Explorer {
pub name: String,
pub url: String,
pub standard: String,
pub explorer_type: SupportedExplorerType,
}

#[derive(Default, Clone)]
pub struct ExplorerApiLib {
pub explorer: Explorer,
pub api_key: String,
pub api_url: String,
pub explorer_type: SupportedExplorerType,
}

impl ExplorerApiLib {
pub fn new(explorer: Explorer, api_key: String) -> Result<Self, Box<dyn std::error::Error>> {
if explorer.name.to_lowercase().contains("blockscout") {
if explorer.explorer_type == SupportedExplorerType::Blockscout {
// blockscout just appends /api to their explorer url
let api_url = format!("{}/api?", explorer.url);
Ok(ExplorerApiLib {
name: explorer.name.to_string(),
url: explorer.url.to_string(),
standard: explorer.standard.to_string(),
explorer,
api_key: api_key.to_string(),
api_url: format!("{}/api?", explorer.url),
explorer_type: SupportedExplorerType::Blockscout,
api_url,
})
} else if explorer.name.to_lowercase().contains("scan") {
} else if explorer.explorer_type == SupportedExplorerType::EtherscanV2 {
let chain_id = STATE_MANAGER
.workflow_state
.lock()
.unwrap()
.chain_id
.clone();
if chain_id.is_some() {
// old Etherscan v1 API code below, let's try the v2 API multichain beta when we have a chain id
// TODO: maybe check supported chain ids and fallback to v1 if the chain id is not supported?
if let Some(chain_id) = chain_id {
return Ok(ExplorerApiLib {
name: explorer.name.to_string(),
url: explorer.url.to_string(),
standard: explorer.standard.to_string(),
explorer,
api_key: api_key.to_string(),
api_url: format!(
"https://api.etherscan.io/v2/api?chainid={}",
chain_id.unwrap()
),
explorer_type: SupportedExplorerType::Etherscan,
api_url: format!("https://api.etherscan.io/v2/api?chainid={}", chain_id),
});
} else {
// etherscan prepends their api url with the api.* subdomain. So for mainnet this would be https://etherscan.io => https://api.etherscan.io. However testnets are also their own subdomain, their subdomains are then prefixed with api- and the explorer url is then used as the suffix, e.g., https://sepolia.etherscan.io => https://api-sepolia.etherscan.io. Some chains are also using a subdomain of etherscan, e.g., Optimism uses https://optimistic.etherscan.io. Here also the dash api- prefix is used. The testnet of optimism doesn't use an additional subdomain: https://sepolia-optimistic.etherscan.io => https://api-sepolia-optimistic.etherscan.io. Some explorers are using their own subdomain, e.g., arbiscan for Arbitrum: https://arbiscan.io => https://api.arbiscan.io.
// TODO: this is kinda error prone, this would catch correct etherscan instances like arbiscan for Arbitrum but there are a lot of other explorers named *something*scan that are not using an etherscan instance and thus don't share the same api endpoints. Maybe get a list of known etherscan-like explorers and their api urls and check if the explorer_url matches any of them?
let slices = explorer.url.split(".").collect::<Vec<&str>>().len();
if slices == 2 {
// we are dealing with https://somethingscan.io
return Ok(ExplorerApiLib {
name: explorer.name.to_string(),
url: explorer.url.to_string(),
standard: explorer.standard.to_string(),
api_key: api_key.to_string(),
api_url: explorer.url.replace("https://", "https://api.").to_string(),
explorer_type: SupportedExplorerType::Etherscan,
});
} else if slices == 3 {
// we are dealing with https://subdomain.somethingscan.io
return Ok(ExplorerApiLib {
name: explorer.name.to_string(),
url: explorer.url.to_string(),
standard: explorer.standard.to_string(),
api_key: api_key.to_string(),
api_url: explorer.url.replace("https://", "https://api-").to_string(),
explorer_type: SupportedExplorerType::Etherscan,
});
} else {
return Err(format!(
"Invalid etherscan url: {} ({})",
explorer.name,
explorer.url,
)
.into());
}
return Err(format!(
"Chain id not found for explorer: {} ({})",
explorer.name, explorer.url,
)
.into());
}
} else if explorer.explorer_type == SupportedExplorerType::Etherscan {
// etherscan prepends their api url with the api.* subdomain. So for mainnet this would be https://etherscan.io => https://api.etherscan.io. However testnets are also their own subdomain, their subdomains are then prefixed with api- and the explorer url is then used as the suffix, e.g., https://sepolia.etherscan.io => https://api-sepolia.etherscan.io. Some chains are also using a subdomain of etherscan, e.g., Optimism uses https://optimistic.etherscan.io. Here also the dash api- prefix is used. The testnet of optimism doesn't use an additional subdomain: https://sepolia-optimistic.etherscan.io => https://api-sepolia-optimistic.etherscan.io. Some explorers are using their own subdomain, e.g., arbiscan for Arbitrum: https://arbiscan.io => https://api.arbiscan.io.
// TODO: this is kinda error prone, this would catch correct etherscan instances like arbiscan for Arbitrum but there are a lot of other explorers named *something*scan that are not using an etherscan instance and thus don't share the same api endpoints. Maybe get a list of known etherscan-like explorers and their api urls and check if the explorer_url matches any of them?
let slices = explorer.url.split(".").collect::<Vec<&str>>().len();
if slices == 2 {
// we are dealing with https://somethingscan.io
let api_url = explorer.url.replace("https://", "https://api.");
return Ok(ExplorerApiLib {
explorer,
api_key: api_key.to_string(),
api_url: format!("{}/api?", api_url),
});
} else if slices == 3 {
// we are dealing with https://subdomain.somethingscan.io
let api_url = explorer.url.replace("https://", "https://api-");
return Ok(ExplorerApiLib {
explorer,
api_key: api_key.to_string(),
api_url: format!("{}/api?", api_url),
});
} else {
return Err(format!(
"Invalid etherscan url: {} ({})",
explorer.name, explorer.url,
)
.into());
}
} else {
return Err(format!(
"Unsupported explorer: {} ({})",
explorer.name,
explorer.url,
)
.into());
return Err(
format!("Unsupported explorer: {} ({})", explorer.name, explorer.url,).into(),
);
}
}

pub async fn get_contract_data(
&self,
contract_address: Address,
) -> Result<(String, String, Option<Constructor>), Box<dyn std::error::Error>> {
if self.explorer_type == SupportedExplorerType::Etherscan
|| self.explorer_type == SupportedExplorerType::Blockscout
if self.explorer.explorer_type == SupportedExplorerType::Etherscan
|| self.explorer.explorer_type == SupportedExplorerType::EtherscanV2
|| self.explorer.explorer_type == SupportedExplorerType::Blockscout
{
let url = format!(
"{}&module=contract&action=getsourcecode&address={}&apikey={}",
Expand Down Expand Up @@ -135,8 +130,7 @@ impl ExplorerApiLib {
}
Err(format!(
"Unsupported explorer: {} ({})",
self.name,
self.url,
self.explorer.name, self.explorer.url,
)
.into())
}
Expand All @@ -145,8 +139,9 @@ impl ExplorerApiLib {
&self,
contract_address: Address,
) -> Result<String, Box<dyn std::error::Error>> {
if self.explorer_type == SupportedExplorerType::Etherscan
|| self.explorer_type == SupportedExplorerType::Blockscout
if self.explorer.explorer_type == SupportedExplorerType::Etherscan
|| self.explorer.explorer_type == SupportedExplorerType::EtherscanV2
|| self.explorer.explorer_type == SupportedExplorerType::Blockscout
{
let url = format!(
"{}&module=contract&action=getcontractcreation&contractaddresses={}&apikey={}",
Expand All @@ -160,13 +155,32 @@ impl ExplorerApiLib {
}
Err(format!(
"Unsupported explorer: {} ({})",
self.name,
self.url,
self.explorer.name, self.explorer.url,
)
.into())
}
}

impl SupportedExplorerType {
pub fn to_env_var_name(&self) -> String {
match self {
SupportedExplorerType::Etherscan => "ETHERSCAN_API_KEY".to_string(),
SupportedExplorerType::EtherscanV2 => "ETHERSCAN_API_KEY".to_string(),
SupportedExplorerType::Blockscout => "BLOCKSCOUT_API_KEY".to_string(),
SupportedExplorerType::Manual => "VERIFIER_API_KEY".to_string(),
}
}

pub fn name(&self) -> String {
match self {
SupportedExplorerType::Etherscan => "Etherscan".to_string(),
SupportedExplorerType::EtherscanV2 => "Etherscan v2".to_string(),
SupportedExplorerType::Blockscout => "Blockscout".to_string(),
SupportedExplorerType::Manual => "".to_string(),
}
}
}

async fn get_etherscan_result(url: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
match reqwest::get(url).await {
Ok(response) => {
Expand All @@ -184,9 +198,7 @@ async fn get_etherscan_result(url: &str) -> Result<serde_json::Value, Box<dyn st
));
Err("Invalid response from etherscan".into())
}
Err(e) => {
Err(format!("Explorer Request Error: {}", e).into())
}
Err(e) => Err(format!("Explorer Request Error: {}", e).into()),
}
}

Expand Down
19 changes: 8 additions & 11 deletions script/cli/src/screens/deploy_contracts/execute_deploy_script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,16 @@ impl ExecuteDeployScriptScreen {
.arg("--verify")
.arg(format!(
"--verifier={}",
if explorer_api.explorer_type == SupportedExplorerType::Blockscout {
if explorer_api.explorer.explorer_type == SupportedExplorerType::Blockscout
{
"blockscout"
} else {
"etherscan"
// custom also works for etherscan
"custom"
}
))
.arg(format!("--verifier-url={}", explorer_api.api_url));
if explorer_api.explorer_type == SupportedExplorerType::Etherscan {
command = command.arg(format!("--etherscan-api-key={}", explorer_api.api_key));
}
.arg(format!("--verifier-url={}", explorer_api.api_url))
.arg(format!("--verifier-api-key={}", explorer_api.api_key));
}

match execute_command(command.arg("--broadcast").arg("--skip-simulation")) {
Expand Down Expand Up @@ -208,9 +208,7 @@ fn execute_command(command: &mut Command) -> Result<Option<String>, Box<dyn std:
}
Ok(None)
}
Err(e) => {
Err(e.to_string().into())
}
Err(e) => Err(e.to_string().into()),
}
}

Expand All @@ -221,8 +219,7 @@ impl Screen for ExecuteDeployScriptScreen {
"Deployment failed: {}\n",
self.execution_error_message.lock().unwrap()
));
buffer
.append_row_text_color("> Press any key to continue", constants::SELECTION_COLOR);
buffer.append_row_text_color("> Press any key to continue", constants::SELECTION_COLOR);
} else {
buffer.append_row_text(&format!(
"{} Executing dry run\n",
Expand Down
Loading
Loading