I'm using web3.py to interact with multiple unknown contracts, where I don't know beforehand if a contract is a proxy contract or the actual contract. I'm getting the ABIs for the contracts on-the-fly from BSCScan's API. Assume I am only interested in contracts whose ABIs are available on BSCscan.
When I retrieve the ABI of a contract from BSCScan, sometimes the ABI has functions like
['admin', 'changeAdmin', 'implementation', 'upgradeTo', 'upgradeToAndCall'] which is how I understand it's a proxy contract address.
Now I'd like to get the address of the implementation contract from the proxy contract. The final goal is to get the ABI of the implementation contract via BSCScan, for which I need the address of the implementation contract.
How do I get the ABI (or the address) of the implementation contract?
(While I am using BSCScan and BSC, for the purposes of this answer I assume the answer for Ethereum and Etherscan is the same)
from bscscan import BscScan from web3 import Web3 contract_address = '0x88f1A5ae2A3BF98AEAF342D26B30a79438c9142e' # Test case def get_abi(contract_address): with BscScan(api_key, asynchronous=False) as client: abi_string = client.get_contract_abi(contract_address) return abi_string web3 = Web3(Web3.HTTPProvider(bsc)) abi_string = get_abi(address) contract = web3.eth.contract(address=address, abi=abi_string) if 'implementation' in [foo.function_identifier for foo in contract.all_functions()]: # it's a proxy contract as its ABI contained implementation() implementation_address = contract.functions.implementation().call() # <-- Error implementation_abi_string = get_abi(implementation_address) contract = web3.eth.contract(address=address, abi=implementation_abi_string) desired_result = contract.functions.somefunction().call()
This results in
Traceback (most recent call last): ... File "helpers.py", line 86, in create_contract_object implementation_address = contract.functions.implementation().call() File "/venv-mac-py/lib/python3.9/site-packages/web3/contract.py", line 957, in call return call_contract_function( File "/venv-mac-py/lib/python3.9/site-packages/web3/contract.py", line 1501, in call_contract_function return_data = web3.eth.call( File "/venv-mac-py/lib/python3.9/site-packages/web3/module.py", line 57, in caller result = w3.manager.request_blocking(method_str, params, error_formatters) File "/venv-mac-py/lib/python3.9/site-packages/web3/manager.py", line 159, in request_blocking apply_error_formatters(error_formatters, response) File "/venv-mac-py/lib/python3.9/site-packages/web3/manager.py", line 63, in apply_error_formatters formatted_response = pipe(response, error_formatters) File "cytoolz/functoolz.pyx", line 667, in cytoolz.functoolz.pipe File "cytoolz/functoolz.pyx", line 642, in cytoolz.functoolz.c_pipe File "/venv-mac-py/lib/python3.9/site-packages/web3/_utils/method_formatters.py", line 544, in raise_solidity_error_on_revert raise ContractLogicError('execution reverted') web3.exceptions.ContractLogicError: execution reverted