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

Add getcontractcreation binding #24

Merged
merged 6 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
56 changes: 55 additions & 1 deletion src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
Client, EtherscanError, Response, Result,
};
use alloy_json_abi::JsonAbi;
use alloy_primitives::{Address, Bytes};
use alloy_primitives::{Address, Bytes, B256};
use semver::Version;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::Path};
Expand Down Expand Up @@ -311,6 +311,23 @@ impl ContractMetadata {
}
}

/// Contract creation data.
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
klkvr marked this conversation as resolved.
Show resolved Hide resolved
#[serde(rename_all = "camelCase")]
pub struct ContractCreationData {
/// The contract's address.
pub contract_address: Address,

/// The contract's deployer address.
/// NOTE: This field contains the address of an EOA that initiated the creation transaction.
/// For contracts deployed by other contracts, the direct deployer address may vary.
pub contract_creator: Address,

/// The hash of the contract creation transaction.
#[serde(rename = "txHash")]
pub transaction_hash: B256,
}

impl Client {
/// Fetches a verified contract's ABI.
///
Expand Down Expand Up @@ -417,4 +434,41 @@ impl Client {

Ok(result)
}

/// Fetches a contract's creation transaction hash and deployer address.
///
/// # Example
///
/// ```no_run
/// # async fn foo(client: foundry_block_explorers::Client) -> Result<(), Box<dyn std::error::Error>> {
/// let address = "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse()?;
/// let creation_data = client.contract_creation_data(address).await?;
/// let deployment_tx = creation_data.transaction_hash;
/// let deployer = creation_data.contract_creator;
/// # Ok(()) }
/// ``
pub async fn contract_creation_data(&self, address: Address) -> Result<ContractCreationData> {
klkvr marked this conversation as resolved.
Show resolved Hide resolved
let query = self.create_query(
"contract",
"getcontractcreation",
HashMap::from([("contractaddresses", address)]),
);

let response = self.get(&query).await?;

// Address is not a contract or contract wasn't indexed yet
if response.contains("No data found") {
return Err(EtherscanError::ContractNotFound(address));
}

let response: Response<Vec<ContractCreationData>> = self.sanitize_response(response)?;

// We are expecting the API to return exactly one result.
let data = response.result.first().ok_or(EtherscanError::EmptyResult {
message: response.message,
status: response.status,
})?;

Ok(*data)
}
}
2 changes: 2 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub enum EtherscanError {
CloudFlareSecurityChallenge,
#[error("Received `Page not found` response. API server is likely down")]
PageNotFound,
#[error("Contract was not found: {0}")]
ContractNotFound(Address),
}

/// etherscan/polyscan is protected by cloudflare, which can lead to html responses like `Sorry, you have been blocked` See also <https://community.cloudflare.com/t/sorry-you-have-been-blocked/110790>
Expand Down
24 changes: 24 additions & 0 deletions tests/it/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,27 @@ async fn can_fetch_contract_source_tree_for_plain_source_code_mapping() {
})
.await
}

#[tokio::test]
#[serial]
async fn can_fetch_contract_creation_data() {
run_with_client(Chain::mainnet(), |client| async move {
client
.contract_creation_data("0xdac17f958d2ee523a2206206994597c13d831ec7".parse().unwrap())
.await
.unwrap();
})
.await
}

#[tokio::test]
#[serial]
async fn error_when_creation_data_for_eoa() {
init_tracing();
run_with_client(Chain::mainnet(), |client| async move {
let addr = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse().unwrap();
let err = client.contract_creation_data(addr).await.unwrap_err();
assert!(matches!(err, EtherscanError::ContractNotFound(_)));
})
.await
}
Loading