2022-03-06 15:21:19 +00:00
use crate ::{
source_tree ::{ SourceTree , SourceTreeEntry } ,
2022-09-29 15:07:50 +00:00
utils ::{
deserialize_address_opt , deserialize_stringified_abi , deserialize_stringified_source_code ,
} ,
2022-03-06 15:21:19 +00:00
Client , EtherscanError , Response , Result ,
} ;
2022-09-29 15:07:50 +00:00
use ethers_core ::{
abi ::{ Abi , Address } ,
types ::{ serde_helpers ::deserialize_stringified_u64 , Bytes } ,
} ;
use semver ::Version ;
use serde ::{ Deserialize , Serialize } ;
use std ::{ collections ::HashMap , path ::Path } ;
2021-11-27 07:54:20 +00:00
2022-09-29 15:07:50 +00:00
#[ cfg(feature = " ethers-solc " ) ]
use ethers_solc ::{ artifacts ::Settings , EvmVersion , Project , ProjectBuilder , SolcConfig } ;
#[ derive(Clone, Debug, Default, Serialize, Deserialize) ]
pub enum SourceCodeLanguage {
#[ default ]
Solidity ,
Vyper ,
}
#[ derive(Clone, Debug, Serialize, Deserialize) ]
pub struct SourceCodeEntry {
pub content : String ,
}
impl < T : Into < String > > From < T > for SourceCodeEntry {
fn from ( s : T ) -> Self {
Self { content : s . into ( ) }
}
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
/// The contract metadata's SourceCode field.
#[ derive(Clone, Debug, Serialize, Deserialize) ]
#[ serde(untagged) ]
pub enum SourceCodeMetadata {
/// Contains metadata and path mapped source code.
Metadata {
/// Programming language of the sources.
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
language : Option < SourceCodeLanguage > ,
/// Source path => source code
#[ serde(default) ]
sources : HashMap < String , SourceCodeEntry > ,
/// Compiler settings, None if the language is not Solidity.
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
settings : Option < serde_json ::Value > ,
} ,
/// Contains only the source code.
SourceCode ( String ) ,
}
impl SourceCodeMetadata {
pub fn source_code ( & self ) -> String {
match self {
Self ::Metadata { sources , .. } = > {
sources . values ( ) . map ( | s | s . content . clone ( ) ) . collect ::< Vec < _ > > ( ) . join ( " \n " )
}
Self ::SourceCode ( s ) = > s . clone ( ) ,
2021-10-17 10:01:20 +00:00
}
}
2022-09-29 15:07:50 +00:00
pub fn language ( & self ) -> Option < SourceCodeLanguage > {
match self {
Self ::Metadata { language , .. } = > language . clone ( ) ,
Self ::SourceCode ( _ ) = > None ,
}
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
pub fn sources ( & self ) -> HashMap < String , SourceCodeEntry > {
match self {
Self ::Metadata { sources , .. } = > sources . clone ( ) ,
Self ::SourceCode ( s ) = > HashMap ::from ( [ ( " Contract " . into ( ) , s . into ( ) ) ] ) ,
2021-10-17 10:01:20 +00:00
}
}
2022-09-29 15:07:50 +00:00
#[ cfg(feature = " ethers-solc " ) ]
pub fn settings ( & self ) -> Result < Option < Settings > > {
match self {
Self ::Metadata { settings , .. } = > match settings {
Some ( value ) = > {
if value . is_null ( ) {
Ok ( None )
} else {
Ok ( Some ( serde_json ::from_value ( value . to_owned ( ) ) ? ) )
}
}
None = > Ok ( None ) ,
} ,
Self ::SourceCode ( _ ) = > Ok ( None ) ,
}
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
#[ cfg(not(feature = " ethers-solc " )) ]
pub fn settings ( & self ) -> Option < & serde_json ::Value > {
match self {
Self ::Metadata { settings , .. } = > settings . as_ref ( ) ,
Self ::SourceCode ( _ ) = > None ,
}
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
}
2021-10-17 10:01:20 +00:00
2022-09-29 15:07:50 +00:00
/// Etherscan contract metadata.
#[ derive(Clone, Debug, Serialize, Deserialize) ]
#[ serde(rename_all = " PascalCase " ) ]
pub struct Metadata {
/// Includes metadata for compiler settings and language.
#[ serde(deserialize_with = " deserialize_stringified_source_code " ) ]
pub source_code : SourceCodeMetadata ,
/// The ABI of the contract.
#[ serde(rename = " ABI " , deserialize_with = " deserialize_stringified_abi " ) ]
pub abi : Abi ,
/// The name of the contract.
pub contract_name : String ,
/// The version that this contract was compiled with. If it is a Vyper contract, it will start
/// with "vyper:".
pub compiler_version : String ,
/// Whether the optimizer was used. This value should only be 0 or 1.
#[ serde(deserialize_with = " deserialize_stringified_u64 " ) ]
pub optimization_used : u64 ,
/// The number of optimizations performed.
#[ serde(deserialize_with = " deserialize_stringified_u64 " ) ]
pub runs : u64 ,
/// The constructor arguments the contract was deployed with.
pub constructor_arguments : Bytes ,
/// The version of the EVM the contract was deployed in. Can be either a variant of
/// [EvmVersion] or "Default" which indicates the compiler's default.
#[ serde(rename = " EVMVersion " ) ]
pub evm_version : String ,
// ?
pub library : String ,
/// The license of the contract.
pub license_type : String ,
/// Whether this contract is a proxy. This value should only be 0 or 1.
#[ serde(deserialize_with = " deserialize_stringified_u64 " ) ]
pub proxy : u64 ,
/// If this contract is a proxy, the address of its implementation.
#[ serde(deserialize_with = " deserialize_address_opt " ) ]
pub implementation : Option < Address > ,
/// The swarm source of the contract.
pub swarm_source : String ,
}
impl Metadata {
/// Returns the contract's source code.
pub fn source_code ( & self ) -> String {
self . source_code . source_code ( )
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
/// Returns the contract's programming language.
pub fn language ( & self ) -> SourceCodeLanguage {
self . source_code . language ( ) . unwrap_or_else ( | | {
if self . is_vyper ( ) {
SourceCodeLanguage ::Vyper
} else {
SourceCodeLanguage ::Solidity
}
} )
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
/// Returns the contract's path mapped source code.
pub fn sources ( & self ) -> HashMap < String , SourceCodeEntry > {
self . source_code . sources ( )
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
/// Parses the compiler version.
pub fn compiler_version ( & self ) -> Result < Version > {
let v = & self . compiler_version ;
let v = v . strip_prefix ( " vyper: " ) . unwrap_or ( v ) ;
let v = v . strip_prefix ( 'v' ) . unwrap_or ( v ) ;
match v . parse ( ) {
Err ( e ) = > {
let v = v . replace ( 'a' , " -alpha. " ) ;
let v = v . replace ( 'b' , " -beta. " ) ;
v . parse ( ) . map_err ( | _ | EtherscanError ::Unknown ( format! ( " bad compiler version: {e} " ) ) )
}
Ok ( v ) = > Ok ( v ) ,
}
}
2021-10-17 10:01:20 +00:00
2022-09-29 15:07:50 +00:00
/// Returns whether this contract is a Vyper or a Solidity contract.
pub fn is_vyper ( & self ) -> bool {
self . compiler_version . starts_with ( " vyper: " )
}
/// Maps this contract's sources to a [SourceTreeEntry] vector.
pub fn source_entries ( & self ) -> Vec < SourceTreeEntry > {
let root = Path ::new ( & self . contract_name ) ;
self . sources ( )
. into_iter ( )
. map ( | ( path , entry ) | {
let path = root . join ( path ) ;
SourceTreeEntry { path , contents : entry . content }
} )
. collect ( )
}
/// Returns the source tree of this contract's sources.
pub fn source_tree ( & self ) -> SourceTree {
SourceTree { entries : self . source_entries ( ) }
}
/// Returns the contract's compiler settings.
#[ cfg(feature = " ethers-solc " ) ]
pub fn settings ( & self ) -> Result < Settings > {
let mut settings = self . source_code . settings ( ) ? . unwrap_or_default ( ) ;
if self . optimization_used = = 1 & & ! settings . optimizer . enabled . unwrap_or_default ( ) {
settings . optimizer . enable ( ) ;
settings . optimizer . runs ( self . runs as usize ) ;
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
settings . evm_version = self . evm_version ( ) ? ;
Ok ( settings )
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
/// Creates a Solc [ProjectBuilder] with this contract's settings.
#[ cfg(feature = " ethers-solc " ) ]
pub fn project_builder ( & self ) -> Result < ProjectBuilder > {
let solc_config = SolcConfig ::builder ( ) . settings ( self . settings ( ) ? ) . build ( ) ;
Ok ( Project ::builder ( ) . solc_config ( solc_config ) )
}
/// Parses the EVM version.
#[ cfg(feature = " ethers-solc " ) ]
pub fn evm_version ( & self ) -> Result < Option < EvmVersion > > {
match self . evm_version . as_str ( ) {
" " | " Default " = > {
Ok ( EvmVersion ::default ( ) . normalize_version ( & self . compiler_version ( ) ? ) )
}
_ = > {
let evm_version = self
. evm_version
. parse ( )
. map_err ( | e | EtherscanError ::Unknown ( format! ( " bad evm version: {e} " ) ) ) ? ;
Ok ( Some ( evm_version ) )
}
}
2021-10-17 10:01:20 +00:00
}
}
2022-08-24 19:50:29 +00:00
#[ derive(Clone, Debug, Serialize, Deserialize) ]
2022-04-06 02:01:44 +00:00
#[ serde(transparent) ]
2021-10-17 10:01:20 +00:00
pub struct ContractMetadata {
pub items : Vec < Metadata > ,
}
impl IntoIterator for ContractMetadata {
type Item = Metadata ;
type IntoIter = std ::vec ::IntoIter < Metadata > ;
fn into_iter ( self ) -> Self ::IntoIter {
self . items . into_iter ( )
}
}
impl ContractMetadata {
2022-09-29 15:07:50 +00:00
/// Returns the ABI of all contracts.
pub fn abis ( & self ) -> Vec < & Abi > {
self . items . iter ( ) . map ( | c | & c . abi ) . collect ( )
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
/// Returns the combined source code of all contracts.
2021-10-17 10:01:20 +00:00
pub fn source_code ( & self ) -> String {
2022-09-29 15:07:50 +00:00
self . items . iter ( ) . map ( | c | c . source_code ( ) ) . collect ::< Vec < _ > > ( ) . join ( " \n " )
2022-03-06 15:21:19 +00:00
}
2022-09-29 15:07:50 +00:00
/// Returns the combined [SourceTree] of all contracts.
pub fn source_tree ( & self ) -> SourceTree {
SourceTree { entries : self . items . iter ( ) . flat_map ( | item | item . source_entries ( ) ) . collect ( ) }
2022-03-06 15:21:19 +00:00
}
2021-10-17 10:01:20 +00:00
}
impl Client {
2022-09-29 15:07:50 +00:00
/// Fetches a verified contract's ABI.
///
/// # Example
2021-10-17 10:01:20 +00:00
///
/// ```no_run
2021-10-24 18:41:50 +00:00
/// # use ethers_etherscan::Client;
/// # use ethers_core::types::Chain;
2021-10-17 10:01:20 +00:00
///
/// # #[tokio::main]
/// # async fn main() {
2021-10-24 18:41:50 +00:00
/// let client = Client::new(Chain::Mainnet, "API_KEY").unwrap();
2021-10-17 10:01:20 +00:00
/// let abi = client
/// .contract_abi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
/// .await.unwrap();
/// # }
/// ```
pub async fn contract_abi ( & self , address : Address ) -> Result < Abi > {
2022-04-06 02:01:44 +00:00
// apply caching
if let Some ( ref cache ) = self . cache {
// If this is None, then we have a cache miss
if let Some ( src ) = cache . get_abi ( address ) {
// If this is None, then the contract is not verified
return match src {
Some ( src ) = > Ok ( src ) ,
None = > Err ( EtherscanError ::ContractCodeNotVerified ( address ) ) ,
}
}
}
2021-10-24 18:41:50 +00:00
let query = self . create_query ( " contract " , " getabi " , HashMap ::from ( [ ( " address " , address ) ] ) ) ;
2021-10-17 10:01:20 +00:00
let resp : Response < String > = self . get_json ( & query ) . await ? ;
2022-04-06 02:01:44 +00:00
if resp . result . starts_with ( " Max rate limit reached " ) {
return Err ( EtherscanError ::RateLimitExceeded )
}
2022-02-24 12:07:34 +00:00
if resp . result . starts_with ( " Contract source code not verified " ) {
2022-04-06 02:01:44 +00:00
if let Some ( ref cache ) = self . cache {
2022-05-06 15:15:49 +00:00
cache . set_abi ( address , None ) ;
2022-04-06 02:01:44 +00:00
}
2022-02-24 12:07:34 +00:00
return Err ( EtherscanError ::ContractCodeNotVerified ( address ) )
}
2022-04-06 02:01:44 +00:00
let abi = serde_json ::from_str ( & resp . result ) ? ;
if let Some ( ref cache ) = self . cache {
2022-05-06 15:15:49 +00:00
cache . set_abi ( address , Some ( & abi ) ) ;
2022-04-06 02:01:44 +00:00
}
Ok ( abi )
2021-10-17 10:01:20 +00:00
}
2022-09-29 15:07:50 +00:00
/// Fetches a contract's verified source code and its metadata.
///
/// # Example
///
2021-10-17 10:01:20 +00:00
/// ```no_run
2021-10-24 18:41:50 +00:00
/// # use ethers_etherscan::Client;
/// # use ethers_core::types::Chain;
2021-10-17 10:01:20 +00:00
///
/// # #[tokio::main]
/// # async fn main() {
2021-10-24 18:41:50 +00:00
/// let client = Client::new(Chain::Mainnet, "API_KEY").unwrap();
2021-10-17 10:01:20 +00:00
/// let meta = client
/// .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
/// .await.unwrap();
/// let code = meta.source_code();
/// # }
/// ```
pub async fn contract_source_code ( & self , address : Address ) -> Result < ContractMetadata > {
2022-04-06 02:01:44 +00:00
// apply caching
if let Some ( ref cache ) = self . cache {
// If this is None, then we have a cache miss
if let Some ( src ) = cache . get_source ( address ) {
// If this is None, then the contract is not verified
return match src {
Some ( src ) = > Ok ( src ) ,
None = > Err ( EtherscanError ::ContractCodeNotVerified ( address ) ) ,
}
}
}
2021-10-29 12:29:35 +00:00
let query =
self . create_query ( " contract " , " getsourcecode " , HashMap ::from ( [ ( " address " , address ) ] ) ) ;
2022-09-29 15:07:50 +00:00
let response = self . get ( & query ) . await ? ;
// Source code is not verified
if response . contains ( " Contract source code not verified " ) {
2022-04-06 02:01:44 +00:00
if let Some ( ref cache ) = self . cache {
2022-05-06 15:15:49 +00:00
cache . set_source ( address , None ) ;
2022-04-06 02:01:44 +00:00
}
2022-03-19 18:32:19 +00:00
return Err ( EtherscanError ::ContractCodeNotVerified ( address ) )
}
2022-09-29 15:07:50 +00:00
let response : Response < ContractMetadata > = self . sanitize_response ( response ) ? ;
let result = response . result ;
2022-04-06 02:01:44 +00:00
if let Some ( ref cache ) = self . cache {
2022-09-29 15:07:50 +00:00
cache . set_source ( address , Some ( & result ) ) ;
2022-04-06 02:01:44 +00:00
}
2022-09-29 15:07:50 +00:00
Ok ( result )
2021-10-17 10:01:20 +00:00
}
}
#[ cfg(test) ]
mod tests {
2022-09-29 15:07:50 +00:00
use super ::* ;
use crate ::tests ::run_at_least_duration ;
2022-04-23 08:42:12 +00:00
use ethers_core ::types ::Chain ;
use serial_test ::serial ;
2022-09-29 15:07:50 +00:00
use std ::time ::Duration ;
/// Abi of [0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413](https://api.etherscan.io/api?module=contract&action=getsourcecode&address=0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413).
const DAO_ABI : & str = " [{ \" constant \" :true, \" inputs \" :[{ \" name \" : \" \" , \" type \" : \" uint256 \" }], \" name \" : \" proposals \" , \" outputs \" :[{ \" name \" : \" recipient \" , \" type \" : \" address \" },{ \" name \" : \" amount \" , \" type \" : \" uint256 \" },{ \" name \" : \" description \" , \" type \" : \" string \" },{ \" name \" : \" votingDeadline \" , \" type \" : \" uint256 \" },{ \" name \" : \" open \" , \" type \" : \" bool \" },{ \" name \" : \" proposalPassed \" , \" type \" : \" bool \" },{ \" name \" : \" proposalHash \" , \" type \" : \" bytes32 \" },{ \" name \" : \" proposalDeposit \" , \" type \" : \" uint256 \" },{ \" name \" : \" newCurator \" , \" type \" : \" bool \" },{ \" name \" : \" yea \" , \" type \" : \" uint256 \" },{ \" name \" : \" nay \" , \" type \" : \" uint256 \" },{ \" name \" : \" creator \" , \" type \" : \" address \" }], \" type \" : \" function \" },{ \" constant \" :false, \" inputs \" :[{ \" name \" : \" _spender \" , \" type \" : \" address \" },{ \" name \" : \" _amount \" , \" type \" : \" uint256 \" }], \" name \" : \" approve \" , \" outputs \" :[{ \" name \" : \" success \" , \" type \" : \" bool \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" minTokensToCreate \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" rewardAccount \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" address \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" daoCreator \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" address \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" totalSupply \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" divisor \" , \" outputs \" :[{ \" name \" : \" divisor \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" extraBalance \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" address \" }], \" type \" : \" function \" },{ \" constant \" :false, \" inputs \" :[{ \" name \" : \" _proposalID \" , \" type \" : \" uint256 \" },{ \" name \" : \" _transactionData \" , \" type \" : \" bytes \" }], \" name \" : \" executeProposal \" , \" outputs \" :[{ \" name \" : \" _success \" , \" type \" : \" bool \" }], \" type \" : \" function \" },{ \" constant \" :false, \" inputs \" :[{ \" name \" : \" _from \" , \" type \" : \" address \" },{ \" name \" : \" _to \" , \" type \" : \" address \" },{ \" name \" : \" _value \" , \" type \" : \" uint256 \" }], \" name \" : \" transferFrom \" , \" outputs \" :[{ \" name \" : \" success \" , \" type \" : \" bool \" }], \" type \" : \" function \" },{ \" constant \" :false, \" inputs \" :[], \" name \" : \" unblockMe \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" bool \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" totalRewardToken \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" actualBalance \" , \" outputs \" :[{ \" name \" : \" _actualBalance \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" closingTime \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[{ \" name \" : \" \" , \" type \" : \" address \" }], \" name \" : \" allowedRecipients \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" bool \" }], \" type \" : \" function \" },{ \" constant \" :false, \" inputs \" :[{ \" name \" : \" _to \" , \" type \" : \" address \" },{ \" name \" : \" _value \" , \" type \" : \" uint256 \" }], \" name \" : \" transferWithoutReward \" , \" outputs \" :[{ \" name \" : \" success \" , \" type \" : \" bool \" }], \" type \" : \" function \" },{ \" constant \" :false, \" inputs \" :[], \" name \" : \" refund \" , \" outputs \" :[], \" type \" : \" function \" },{ \" constant \" :false, \" inputs \" :[{ \" name \" : \" _recipient \" , \" type \" : \" address \" },{ \" name \" : \" _amount \" , \" type \" : \" uint256 \" },{ \" name \" : \" _description \" , \" type \" : \" string \" },{ \" name \" : \" _transactionData \" , \" type \" : \" bytes \" },{ \" name \" : \" _debatingPeriod \" , \" type \" : \" uint256 \" },{ \" name \" : \" _newCurator \" , \" type \" : \" bool \" }], \" name \" : \" newProposal \" , \" outputs \" :[{ \" name \" : \" _proposalID \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[{ \" name \" : \" \" , \" type \" : \" address \" }], \" name \" : \" DAOpaidOut \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :true, \" inputs \" :[], \" name \" : \" minQuorumDivisor \" , \" outputs \" :[{ \" name \" : \" \" , \" type \" : \" uint256 \" }], \" type \" : \" function \" },{ \" constant \" :false, \" inputs \" :[{ \" name \" : \" _newContract \" , \" type \" : \" address \" }], \" name \" : \" newCon
2021-11-27 07:54:20 +00:00
2022-04-23 08:42:12 +00:00
#[ allow(unused) ]
fn init_tracing ( ) {
tracing_subscriber ::fmt ( )
. with_env_filter ( tracing_subscriber ::EnvFilter ::from_default_env ( ) )
. init ( ) ;
}
2021-11-27 07:54:20 +00:00
2022-04-23 08:42:12 +00:00
#[ tokio::test ]
#[ serial ]
#[ ignore ]
async fn can_fetch_ftm_contract_abi ( ) {
init_tracing ( ) ;
run_at_least_duration ( Duration ::from_millis ( 250 ) , async {
let client = Client ::new_from_env ( Chain ::Fantom ) . unwrap ( ) ;
2021-10-17 10:01:20 +00:00
2022-04-23 08:42:12 +00:00
let _abi = client
. contract_abi ( " 0x80AA7cb0006d5DDD91cce684229Ac6e398864606 " . parse ( ) . unwrap ( ) )
. await
. unwrap ( ) ;
} )
. await ;
}
2021-11-27 07:54:20 +00:00
2021-10-17 10:01:20 +00:00
#[ tokio::test ]
2021-11-27 07:54:20 +00:00
#[ serial ]
2021-10-17 10:01:20 +00:00
#[ ignore ]
async fn can_fetch_contract_abi ( ) {
2022-09-29 15:07:50 +00:00
init_tracing ( ) ;
2021-11-27 07:54:20 +00:00
run_at_least_duration ( Duration ::from_millis ( 250 ) , async {
let client = Client ::new_from_env ( Chain ::Mainnet ) . unwrap ( ) ;
2022-09-29 15:07:50 +00:00
let abi = client
2021-11-27 07:54:20 +00:00
. contract_abi ( " 0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413 " . parse ( ) . unwrap ( ) )
. await
. unwrap ( ) ;
2022-09-29 15:07:50 +00:00
assert_eq! ( abi , serde_json ::from_str ( DAO_ABI ) . unwrap ( ) ) ;
2021-11-27 07:54:20 +00:00
} )
. await ;
2021-10-17 10:01:20 +00:00
}
#[ tokio::test ]
2021-11-27 07:54:20 +00:00
#[ serial ]
2021-10-17 10:01:20 +00:00
#[ ignore ]
async fn can_fetch_contract_source_code ( ) {
2022-09-29 15:07:50 +00:00
init_tracing ( ) ;
2021-11-27 07:54:20 +00:00
run_at_least_duration ( Duration ::from_millis ( 250 ) , async {
let client = Client ::new_from_env ( Chain ::Mainnet ) . unwrap ( ) ;
2022-09-29 15:07:50 +00:00
let meta = client
2021-11-27 07:54:20 +00:00
. contract_source_code ( " 0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413 " . parse ( ) . unwrap ( ) )
. await
. unwrap ( ) ;
2022-09-29 15:07:50 +00:00
assert_eq! ( meta . items . len ( ) , 1 ) ;
let item = & meta . items [ 0 ] ;
assert! ( matches! ( item . source_code , SourceCodeMetadata ::SourceCode ( _ ) ) ) ;
assert_eq! ( item . source_code . sources ( ) . len ( ) , 1 ) ;
assert_eq! ( item . abi , serde_json ::from_str ( DAO_ABI ) . unwrap ( ) ) ;
2021-11-27 07:54:20 +00:00
} )
. await
2021-10-17 10:01:20 +00:00
}
2022-03-19 18:32:19 +00:00
#[ tokio::test ]
#[ serial ]
#[ ignore ]
async fn can_get_error_on_unverified_contract ( ) {
2022-09-29 15:07:50 +00:00
init_tracing ( ) ;
2022-03-19 18:32:19 +00:00
run_at_least_duration ( Duration ::from_millis ( 250 ) , async {
let client = Client ::new_from_env ( Chain ::Mainnet ) . unwrap ( ) ;
2022-09-29 15:07:50 +00:00
let addr = " 0xb5c31a0e22cae98ac08233e512bd627885aa24e5 " . parse ( ) . unwrap ( ) ;
let err = client . contract_source_code ( addr ) . await . unwrap_err ( ) ;
assert! ( matches! ( err , EtherscanError ::ContractCodeNotVerified ( _ ) ) ) ;
2022-03-19 18:32:19 +00:00
} )
. await
}
2022-03-06 15:21:19 +00:00
/// Query a contract that has a single string source entry instead of underlying JSON metadata.
#[ tokio::test ]
#[ serial ]
#[ ignore ]
async fn can_fetch_contract_source_tree_for_singleton_contract ( ) {
2022-09-29 15:07:50 +00:00
init_tracing ( ) ;
2022-03-06 15:21:19 +00:00
run_at_least_duration ( Duration ::from_millis ( 250 ) , async {
let client = Client ::new_from_env ( Chain ::Mainnet ) . unwrap ( ) ;
let meta = client
. contract_source_code ( " 0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413 " . parse ( ) . unwrap ( ) )
. await
. unwrap ( ) ;
2022-09-29 15:07:50 +00:00
assert_eq! ( meta . items . len ( ) , 1 ) ;
let item = & meta . items [ 0 ] ;
assert! ( matches! ( item . source_code , SourceCodeMetadata ::SourceCode ( _ ) ) ) ;
assert_eq! ( item . source_code . sources ( ) . len ( ) , 1 ) ;
assert_eq! ( item . abi , serde_json ::from_str ( DAO_ABI ) . unwrap ( ) ) ;
2022-03-06 15:21:19 +00:00
} )
. await
}
/// Query a contract that has many source entries as JSON metadata and ensure they are
/// reflected.
#[ tokio::test ]
#[ serial ]
#[ ignore ]
async fn can_fetch_contract_source_tree_for_multi_entry_contract ( ) {
2022-09-29 15:07:50 +00:00
init_tracing ( ) ;
2022-03-06 15:21:19 +00:00
run_at_least_duration ( Duration ::from_millis ( 250 ) , async {
let client = Client ::new_from_env ( Chain ::Mainnet ) . unwrap ( ) ;
let meta = client
. contract_source_code ( " 0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63 " . parse ( ) . unwrap ( ) )
. await
. unwrap ( ) ;
2022-09-29 15:07:50 +00:00
assert_eq! ( meta . items . len ( ) , 1 ) ;
assert! ( matches! ( meta . items [ 0 ] . source_code , SourceCodeMetadata ::Metadata { .. } ) ) ;
let source_tree = meta . source_tree ( ) ;
2022-03-06 15:21:19 +00:00
assert_eq! ( source_tree . entries . len ( ) , 15 ) ;
} )
. await
}
2021-10-17 10:01:20 +00:00
}