如何從代理合約地址獲取執行合約地址?
我正在使用 web3.py 與多個未知契約進行互動,我事先不知道契約是代理契約還是實際契約。我正在從 BSCScan 的 API 即時獲取契約的 ABI。假設我只對 BSCscan 上提供 ABI 的合約感興趣。
當我從 BSCScan 檢索契約的 ABI 時,有時 ABI 具有類似
['admin', 'changeAdmin', 'implementation', 'upgradeTo', 'upgradeToAndCall']
我理解它是代理契約地址的功能。現在我想從代理合約中獲取實現合約的地址。最終目標是通過 BSCScan 獲取實現合約的 ABI,為此我需要實現合約的地址。
如何獲取執行合約的 ABI(或地址)?
(雖然我使用的是 BSCScan 和 BSC,但出於此答案的目的,我假設 Ethereum 和 Etherscan 的答案是相同的)
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()
這導致
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
大多數代理合約通常都有一個公共變數,定義為:
address public implementation;
其中定義了實現合約的地址。然後,您可以在 python 中將其稱為視圖函式,例如:
proxy_contract = web3.eth.contract(address=address, abi=abi_string) implementation_contract_address = proxy_contract.functions.implementation.call()
但是,特別是這個合約沒有
implementation
功能,並且有一些私有功能。這是他們呼叫
delegatecall
實現的代理合約的程式碼:bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev Returns the current implementation address. */ function _implementation() internal override view returns (address impl) { bytes32 slot = _IMPLEMENTATION_SLOT; // solhint-disable-next-line no-inline-assembly assembly { impl := sload(slot) } }
因此,您必須直接從區塊鏈的儲存中讀取以了解其內容
_IMPLEMENTATION_SLOT
。我們還需要了解一些 yul/assembly 以了解發生了什麼。
sload(p)
載入儲存在 location 中的值p
。在這個合約中,這意味著實現合約地址儲存0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
在記憶體中的位置。我們可以通過查看
_setImplementation
儲存新實現地址的方法來確認這一點sstore
(更多的 Yul 程式碼,它在儲存位置設置值)。function _setImplementation(address newImplementation) private { require(Address.isContract(newImplementation), "UpgradeableProxy: new implementation is not a contract"); bytes32 slot = _IMPLEMENTATION_SLOT; // solhint-disable-next-line no-inline-assembly assembly { sstore(slot, newImplementation) } }
因此,我們所要做的就是讀取位於該合約儲存中定義的位置的地址
_IMPLEMENTATION_SLOT
。我們可以通過以下方式找到:
def main(): web3 = Web3(Web3.HTTPProvider("https://bsc-dataseed2.defibit.io/")) impl_contract = Web3.toHex( web3.eth.get_storage_at( contract_address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", ) ) print(impl_contract)
我們得到:
0x000000000000000000000000ba5fe23f8a3a24bed3236f05f2fcf35fd0bf0b5c
然後我們只需撤消 0 填充,以獲取: 的地址
0xba5fe23f8a3a24bed3236f05f2fcf35fd0bf0b5c
。這確實是執行契約。編輯
需要注意的是,很多合約都會使用這個
IMPLEMENTATION_SLOT
,因為它遵循ERC1967的指導方針