Skip to content

Commit

Permalink
Add getcontractcreation binding (#24)
Browse files Browse the repository at this point in the history
This PR adds binding for
[getcontractcreation](https://docs.etherscan.io/api-endpoints/contracts#get-contract-creator-and-creation-tx-hash)
method which returns contract deployer and creation tx.

I will need this to complete [#6344 foundry
issue](foundry-rs/foundry#6334).

I have only added binding and support for fetching data for single
contract but Etherscan supports fetching those for up to 5 contracts at
a time. Considering such small restriction, does it make sense to
implement binding for multi-contract requests too?

---------

Co-authored-by: evalir <[email protected]>
  • Loading branch information
klkvr and Evalir authored Jan 4, 2024
1 parent e37a7da commit 478df0f
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 1 deletion.
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)]
#[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> {
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 @@ -158,3 +158,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
}

0 comments on commit 478df0f

Please sign in to comment.