@@ -554,48 +554,39 @@ def get(
554
554
return None
555
555
556
556
if contract_type := self .contract_types [address_key ]:
557
+ # The ContractType was previously cached.
557
558
if default and default != contract_type :
558
- # Replacing contract type
559
- self .contract_types [address_key ] = default
560
- return default
559
+ # The given default ContractType is different than the cached one.
560
+ # Merge the two and cache the merged result.
561
+ combined_contract_type = _merge_contract_types (contract_type , default )
562
+ self .contract_types [address_key ] = combined_contract_type
563
+ return combined_contract_type
561
564
562
565
return contract_type
563
566
564
- else :
565
- # Contract is not cached yet. Check broader sources, such as an explorer.
566
- if not proxy_info and detect_proxy :
567
- # Proxy info not provided. Attempt to detect.
568
- if not (proxy_info := self .proxy_infos [address_key ]):
569
- if proxy_info := self .provider .network .ecosystem .get_proxy_info (address_key ):
570
- self .proxy_infos [address_key ] = proxy_info
571
-
572
- if proxy_info :
573
- # Contract is a proxy (either was detected or provided).
574
- implementation_contract_type = self .get (proxy_info .target , default = default )
575
- proxy_contract_type = (
576
- self ._get_contract_type_from_explorer (address_key )
577
- if fetch_from_explorer
578
- else None
579
- )
580
- if proxy_contract_type is not None and implementation_contract_type is not None :
581
- combined_contract = _get_combined_contract_type (
582
- proxy_contract_type , proxy_info , implementation_contract_type
583
- )
584
- self .contract_types [address_key ] = combined_contract
585
- return combined_contract
586
-
587
- elif implementation_contract_type is not None :
588
- contract_type_to_cache = implementation_contract_type
589
- self .contract_types [address_key ] = implementation_contract_type
590
- return contract_type_to_cache
591
-
592
- elif proxy_contract_type is not None :
593
- self .contract_types [address_key ] = proxy_contract_type
594
- return proxy_contract_type
595
-
596
- # Also gets cached to disk for faster lookup next time.
597
- if fetch_from_explorer :
598
- contract_type = self ._get_contract_type_from_explorer (address_key )
567
+ # Contract is not cached yet. Check broader sources, such as an explorer.
568
+ if not proxy_info and detect_proxy :
569
+ # Proxy info not provided. Attempt to detect.
570
+ if not (proxy_info := self .proxy_infos [address_key ]):
571
+ if proxy_info := self .provider .network .ecosystem .get_proxy_info (address_key ):
572
+ self .proxy_infos [address_key ] = proxy_info
573
+
574
+ if proxy_info :
575
+ if proxy_contract_type := self ._get_proxy_contract_type (
576
+ address_key ,
577
+ proxy_info ,
578
+ fetch_from_explorer = fetch_from_explorer ,
579
+ default = default ,
580
+ ):
581
+ # `proxy_contract_type` is one of the following:
582
+ # 1. A ContractType with the combined proxy and implementation ABIs
583
+ # 2. Implementation-only ABI ContractType (like forwarder proxies)
584
+ # 3. Proxy only ABI (e.g. unverified implementation ContractType)
585
+ return proxy_contract_type
586
+
587
+ # Also gets cached to disk for faster lookup next time.
588
+ if fetch_from_explorer :
589
+ contract_type = self ._get_contract_type_from_explorer (address_key )
599
590
600
591
# Cache locally for faster in-session look-up.
601
592
if contract_type :
@@ -606,6 +597,65 @@ def get(
606
597
607
598
return contract_type
608
599
600
+ def _get_proxy_contract_type (
601
+ self ,
602
+ address : AddressType ,
603
+ proxy_info : ProxyInfoAPI ,
604
+ fetch_from_explorer : bool = True ,
605
+ default : Optional [ContractType ] = None ,
606
+ ) -> Optional [ContractType ]:
607
+ """
608
+ Combines the discoverable ABIs from the proxy contract and its implementation.
609
+ """
610
+ implementation_contract_type = self ._get_contract_type (
611
+ proxy_info .target ,
612
+ fetch_from_explorer = fetch_from_explorer ,
613
+ default = default ,
614
+ )
615
+ proxy_contract_type = self ._get_contract_type (
616
+ address , fetch_from_explorer = fetch_from_explorer
617
+ )
618
+ if proxy_contract_type is not None and implementation_contract_type is not None :
619
+ combined_contract = _get_combined_contract_type (
620
+ proxy_contract_type , proxy_info , implementation_contract_type
621
+ )
622
+ self .contract_types [address ] = combined_contract
623
+ return combined_contract
624
+
625
+ elif implementation_contract_type is not None :
626
+ contract_type_to_cache = implementation_contract_type
627
+ self .contract_types [address ] = implementation_contract_type
628
+ return contract_type_to_cache
629
+
630
+ elif proxy_contract_type is not None :
631
+ # In this case, the implementation ContactType was not discovered.
632
+ # However, we were able to discover the ContractType of the proxy.
633
+ # Proceed with caching the proxy; the user can update the type later
634
+ # when the implementation is discoverable.
635
+ self .contract_types [address ] = proxy_contract_type
636
+ return proxy_contract_type
637
+
638
+ logger .warning (f"Unable to determine the ContractType for the proxy at '{ address } '." )
639
+ return None
640
+
641
+ def _get_contract_type (
642
+ self ,
643
+ address : AddressType ,
644
+ fetch_from_explorer : bool = True ,
645
+ default : Optional [ContractType ] = None ,
646
+ ) -> Optional [ContractType ]:
647
+ """
648
+ Get the _exact_ ContractType for a given address. For proxy contracts, returns
649
+ the proxy ABIs if there are any and not the implementation ABIs.
650
+ """
651
+ if contract_type := self .contract_types [address ]:
652
+ return contract_type
653
+
654
+ elif fetch_from_explorer :
655
+ return self ._get_contract_type_from_explorer (address )
656
+
657
+ return default
658
+
609
659
@classmethod
610
660
def get_container (cls , contract_type : ContractType ) -> ContractContainer :
611
661
"""
@@ -859,6 +909,16 @@ def _get_contract_type_from_explorer(self, address: AddressType) -> Optional[Con
859
909
860
910
if contract_type :
861
911
# Cache contract so faster look-up next time.
912
+ if not isinstance (contract_type , ContractType ):
913
+ explorer_name = self .provider .network .explorer .name
914
+ wrong_type = type (contract_type )
915
+ wrong_type_str = getattr (wrong_type , "__name__" , f"{ wrong_type } " )
916
+ logger .warning (
917
+ f"Explorer '{ explorer_name } ' returned unexpected "
918
+ f"type '{ wrong_type_str } ' ContractType."
919
+ )
920
+ return None
921
+
862
922
self .contract_types [address ] = contract_type
863
923
864
924
return contract_type
@@ -869,16 +929,31 @@ def _get_combined_contract_type(
869
929
proxy_info : ProxyInfoAPI ,
870
930
implementation_contract_type : ContractType ,
871
931
) -> ContractType :
872
- proxy_abis = [
873
- abi for abi in proxy_contract_type .abi if abi .type in ("error" , "event" , "function" )
874
- ]
932
+ proxy_abis = _get_relevant_additive_abis (proxy_contract_type )
875
933
876
934
# Include "hidden" ABIs, such as Safe's `masterCopy()`.
877
935
if proxy_info .abi and proxy_info .abi .signature not in [
878
936
abi .signature for abi in implementation_contract_type .abi
879
937
]:
880
938
proxy_abis .append (proxy_info .abi )
881
939
882
- combined_contract_type = implementation_contract_type .model_copy (deep = True )
883
- combined_contract_type .abi .extend (proxy_abis )
884
- return combined_contract_type
940
+ return _merge_abis (implementation_contract_type , proxy_abis )
941
+
942
+
943
+ def _get_relevant_additive_abis (contract_type : ContractType ) -> list [ABI ]:
944
+ # Get ABIs you would want to add to a base contract as extra,
945
+ # such as unique ABIs from proxies.
946
+ return [abi for abi in contract_type .abi if abi .type in ("error" , "event" , "function" )]
947
+
948
+
949
+ def _merge_abis (base_contract : ContractType , extra_abis : list [ABI ]) -> ContractType :
950
+ contract_type = base_contract .model_copy (deep = True )
951
+ contract_type .abi .extend (extra_abis )
952
+ return contract_type
953
+
954
+
955
+ def _merge_contract_types (
956
+ base_contract_type : ContractType , additive_contract_type : ContractType
957
+ ) -> ContractType :
958
+ relevant_abis = _get_relevant_additive_abis (additive_contract_type )
959
+ return _merge_abis (base_contract_type , relevant_abis )
0 commit comments