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
Best Answer
Most proxy contracts typically have a public variable defined as a:
Which defines the address of the implementation contract. You could then call it as a view function in python, with something like:
However, this contract, in particular, doesn't have an
implementation
function and has some private functions.Here is the code for their proxy contract where they call the
delegatecall
to the implementation:So you'd have to read directly off the storage of the blockchain to understand what
_IMPLEMENTATION_SLOT
is. We also need to know some yul/assembly to understand what's going on.sload(p)
loads the value that is stored in storage at locationp
. In this contract, this means the implementation contract address is stored at location0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
in memory.We can confirm this, by looking at the
_setImplementation
method, where it stores the new implementation address withsstore
(more Yul code, that sets a value at a location in storage).So, all we have to do to read this, is read the address that is located in the storage of this contract at the location defined in
_IMPLEMENTATION_SLOT
.We can find this with:
To which we get:
0x000000000000000000000000ba5fe23f8a3a24bed3236f05f2fcf35fd0bf0b5c
Which we then just undo the 0 padding, to get the address of:
0xba5fe23f8a3a24bed3236f05f2fcf35fd0bf0b5c
. Which is indeed the implementation contract.Edit
It should be noted, that a lot of contracts will use this
IMPLEMENTATION_SLOT
, as it follows the guidelines of the ERC1967