Skip to content

Conversation

@avorylli
Copy link
Contributor

Summary

Adds Sourcify API support to forge clone command, allowing users to clone contracts from Sourcify in addition to Etherscan.

Changes

  • Added --source CLI option to choose between etherscan (default) and sourcify
  • Implemented SourcifyClient that implements EtherscanClient trait
  • Added methods to fetch contract source code and creation data from Sourcify API
  • Updated CloneArgs::run to support both data sources

Usage

Use Etherscan (default)

forge clone

Use Sourcify

forge clone

--source sourcify

Closes #12860

Added support for Sourcify API in `forge clone` command.
@avorylli
Copy link
Contributor Author

will try fixing ci's just need to understand if i'm solving the issue the right way

@zerosnacks
Copy link
Member

Hi @avorylli thanks for your PR, your approach looks good

@avorylli
Copy link
Contributor Author

@zerosnacks fixed CI, thanks for the review!

Copy link
Collaborator

@grandizzy grandizzy left a 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

&self,
address: Address,
) -> std::result::Result<ContractMetadata, EtherscanError> {
let url = self.get_contract_url(address, "files,abi,compilation");
Copy link
Collaborator

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?;
Copy link
Collaborator

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

let response = self.client.get(&url).send().await?;

let status = response.status();
match status.as_u16() {
Copy link
Collaborator

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

error_id: String,
}

impl EtherscanClient for SourcifyClient {
Copy link
Collaborator

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

Self {
client: reqwest::Client::new(),
chain,
base_url: "https://sourcify.dev/server".to_string(),
Copy link
Collaborator

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

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())
}
) and let's reuse the default URL constant

#[serde(default)]
name: String,
#[serde(default)]
settings: Option<serde_json::Value>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)));
}

let response_data: SourcifyContractResponse = response.json().await?;
Copy link
Collaborator

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

// 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

feat: support sourcify in forge clone

3 participants