-
Notifications
You must be signed in to change notification settings - Fork 2.3k
feat: add Sourcify support to forge clone #12900
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
base: master
Are you sure you want to change the base?
Conversation
Added support for Sourcify API in `forge clone` command.
|
will try fixing ci's just need to understand if i'm solving the issue the right way |
|
Hi @avorylli thanks for your PR, your approach looks good |
Removed unused import of BTreeMap.
|
@zerosnacks fixed CI, thanks for the review! |
grandizzy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@avorylli thank you for your PR! left some things to be changed, pls have a look
crates/forge/src/cmd/clone.rs
Outdated
| &self, | ||
| address: Address, | ||
| ) -> std::result::Result<ContractMetadata, EtherscanError> { | ||
| let url = self.get_contract_url(address, "files,abi,compilation"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
field name should be sources instead files: "sources,abi,compilation"
| address: Address, | ||
| ) -> std::result::Result<ContractMetadata, EtherscanError> { | ||
| let url = self.get_contract_url(address, "files,abi,compilation"); | ||
| let response = self.client.get(&url).send().await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's trace response here to better debug
crates/forge/src/cmd/clone.rs
Outdated
| let response = self.client.get(&url).send().await?; | ||
|
|
||
| let status = response.status(); | ||
| match status.as_u16() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can match on reqwest constants, e.g.
match response.status() {
StatusCode::NOT_FOUND => return Err(EtherscanError::ContractCodeNotVerified(address)),
StatusCode::TOO_MANY_REQUESTS => return Err(EtherscanError::RateLimitExceeded),
}also not sure sourcify can rate limit, but maybe OK to keep
crates/forge/src/cmd/clone.rs
Outdated
| error_id: String, | ||
| } | ||
|
|
||
| impl EtherscanClient for SourcifyClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's rename EtherscanClient trait to ExplorerClient and have impl ExplorerClient for Client / impl ExplorerClient for SourcifyClient
crates/forge/src/cmd/clone.rs
Outdated
| Self { | ||
| client: reqwest::Client::new(), | ||
| chain, | ||
| base_url: "https://sourcify.dev/server".to_string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's make this configurable so other explorers sourcify compatible API would work too (see
foundry/crates/verify/src/sourcify.rs
Lines 190 to 196 in c63d422
| impl SourcifyVerificationProvider { | |
| fn get_base_url(verifier_url: Option<&str>) -> Url { | |
| // note(onbjerg): a little ugly but makes this infallible as we guarantee `SOURCIFY_URL` to | |
| // be well formatted | |
| Url::parse(verifier_url.unwrap_or(SOURCIFY_URL)) | |
| .unwrap_or_else(|_| Url::parse(SOURCIFY_URL).unwrap()) | |
| } |
crates/forge/src/cmd/clone.rs
Outdated
| #[serde(default)] | ||
| name: String, | ||
| #[serde(default)] | ||
| settings: Option<serde_json::Value>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be compiler_settings see https://sourcify.dev/server/v2/contract/1/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec?fields=sources,abi,compilation for a full response example
crates/forge/src/cmd/clone.rs
Outdated
| ))); | ||
| } | ||
|
|
||
| let response_data: SourcifyContractResponse = response.json().await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't properly deserialize, you can test with forge clone --source sourcify 0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec. Let's also add this as a test similar to etherscan tests here
foundry/crates/forge/tests/cli/cmd.rs
Lines 698 to 815 in c63d422
| // checks that clone works | |
| forgetest!(can_clone, |prj, cmd| { | |
| prj.wipe(); | |
| let foundry_toml = prj.root().join(Config::FILE_NAME); | |
| assert!(!foundry_toml.exists()); | |
| cmd.args([ | |
| "clone", | |
| "--etherscan-api-key", | |
| next_etherscan_api_key().as_str(), | |
| "0x044b75f554b886A065b9567891e45c79542d7357", | |
| ]) | |
| .arg(prj.root()) | |
| .assert_success() | |
| .stdout_eq(str![[r#" | |
| Downloading the source code of 0x044b75f554b886A065b9567891e45c79542d7357 from Etherscan... | |
| Initializing [..]... | |
| Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) | |
| Installed forge-std[..] | |
| Initialized forge project | |
| Collecting the creation information of 0x044b75f554b886A065b9567891e45c79542d7357 from Etherscan... | |
| [COMPILING_FILES] with [SOLC_VERSION] | |
| [SOLC_VERSION] [ELAPSED] | |
| Compiler run successful! | |
| "#]]); | |
| let s = read_string(&foundry_toml); | |
| let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; | |
| }); | |
| // Checks that quiet mode does not print anything for clone | |
| forgetest!(can_clone_quiet, |prj, cmd| { | |
| prj.wipe(); | |
| cmd.args([ | |
| "clone", | |
| "--etherscan-api-key", | |
| next_etherscan_api_key().as_str(), | |
| "--quiet", | |
| "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", | |
| ]) | |
| .arg(prj.root()) | |
| .assert_empty_stdout(); | |
| }); | |
| // checks that clone works with --no-remappings-txt | |
| forgetest!(can_clone_no_remappings_txt, |prj, cmd| { | |
| prj.wipe(); | |
| let foundry_toml = prj.root().join(Config::FILE_NAME); | |
| assert!(!foundry_toml.exists()); | |
| cmd.args([ | |
| "clone", | |
| "--etherscan-api-key", | |
| next_etherscan_api_key().as_str(), | |
| "--no-remappings-txt", | |
| "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", | |
| ]) | |
| .arg(prj.root()) | |
| .assert_success() | |
| .stdout_eq(str![[r#" | |
| Downloading the source code of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from Etherscan... | |
| Initializing [..]... | |
| Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None) | |
| Installed forge-std[..] | |
| Initialized forge project | |
| Collecting the creation information of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from Etherscan... | |
| [COMPILING_FILES] with [SOLC_VERSION] | |
| [SOLC_VERSION] [ELAPSED] | |
| Compiler run successful! | |
| "#]]); | |
| let s = read_string(&foundry_toml); | |
| let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; | |
| }); | |
| // checks that clone works with --keep-directory-structure | |
| forgetest!(can_clone_keep_directory_structure, |prj, cmd| { | |
| prj.wipe(); | |
| let foundry_toml = prj.root().join(Config::FILE_NAME); | |
| assert!(!foundry_toml.exists()); | |
| let output = cmd | |
| .forge_fuse() | |
| .args([ | |
| "clone", | |
| "--etherscan-api-key", | |
| next_etherscan_api_key().as_str(), | |
| "--keep-directory-structure", | |
| "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", | |
| ]) | |
| .arg(prj.root()) | |
| .assert_success() | |
| .get_output() | |
| .stdout_lossy(); | |
| if output.contains("502 Bad Gateway") { | |
| // etherscan nginx proxy issue, skip this test: | |
| // | |
| // stdout: | |
| // Downloading the source code of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from | |
| // Etherscan... 2024-07-05T11:40:11.801765Z ERROR etherscan: Failed to deserialize | |
| // response: expected value at line 1 column 1 res="<html>\r\n<head><title>502 Bad | |
| // Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad | |
| // Gateway</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n" | |
| eprintln!("Skipping test due to 502 Bad Gateway"); | |
| return; | |
| } | |
| let s = read_string(&foundry_toml); | |
| let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; | |
| }); |
Add test for cloning with sourcify source
Updated the Sourcify client to cache creation data and reuse it across API calls, improving efficiency. Modified the contract source code retrieval to include additional creation data fields.
Refactor contract source code and creation data retrieval to use fallback values when API requests fail or fields are unavailable.
Updated contract_source_code to include additional fields in the API request and improved caching of creation data. Removed fallback logic for fetching creation data from the API.
Removed redundant creation_data initialization and caching.
Summary
Adds Sourcify API support to
forge clonecommand, allowing users to clone contracts from Sourcify in addition to Etherscan.Changes
--sourceCLI option to choose betweenetherscan(default) andsourcifySourcifyClientthat implementsEtherscanClienttraitCloneArgs::runto support both data sourcesUsage
Use Etherscan (default)
forge clone
Use Sourcify
forge clone
--source sourcifyCloses #12860