ethereum / web3.py

A python interface for interacting with the Ethereum blockchain and ecosystem.
http://web3py.readthedocs.io
MIT License
4.97k stars 1.69k forks source link

[BUG] Unexpected TransparentUpgradeableProxy contract functions call() reverted by EVM #3051

Closed viper7882 closed 1 year ago

viper7882 commented 1 year ago
aiohttp==3.8.3
aiosignal==1.2.0
asttokens==2.0.5
async-timeout==4.0.2
atomicwrites==1.4.1
attrs==22.1.0
base58==2.1.1
bitarray==2.6.0
black==22.10.0
certifi==2022.9.24
charset-normalizer==2.1.1
click==8.1.3
colorama==0.4.6
Cython==0.29.34
cytoolz==0.12.0
dataclassy==0.11.1
eip712==0.1.0
eth-abi==2.2.0
eth-account==0.5.9
eth-brownie==1.19.3
eth-event==1.2.3
eth-hash==0.3.3
eth-keyfile==0.5.1
eth-keys==0.3.4
eth-rlp==0.2.1
eth-typing==2.3.0
eth-utils==1.10.0
exceptiongroup==1.1.1
execnet==1.9.0
frozenlist==1.3.1
hexbytes==0.2.3
hypothesis==6.27.3
idna==3.4
inflection==0.5.0
iniconfig==1.1.1
ipfshttpclient==0.8.0a2
jsonschema==3.2.0
lazy-object-proxy==1.7.1
lru-dict==1.1.8
multiaddr==0.0.9
multidict==6.0.2
mypy-extensions==0.4.3
mysql-connector-python==8.0.32
mythx-models==1.9.1
netaddr==0.8.0
packaging==21.3
parsimonious==0.8.1
pathspec==0.10.1
platformdirs==2.5.2
pluggy==1.0.0
prompt-toolkit==3.0.31
protobuf==3.19.5
psutil==5.9.2
py==1.11.0
py-solc-ast==1.2.9
py-solc-x==1.1.1
pycryptodome==3.15.0
Pygments==2.13.0
pygments-lexer-solidity==0.7.0
PyJWT==1.7.1
pyparsing==3.0.9
pyrsistent==0.18.1
pytest==6.2.5
pytest-forked==1.4.0
pytest-xdist==1.34.0
python-dateutil==2.8.1
python-dotenv==0.16.0
pythx==1.6.1
pywin32==306
PyYAML==5.4.1
requests==2.28.1
rlp==2.0.1
semantic-version==2.10.0
six==1.16.0
sortedcontainers==2.4.0
toml==0.10.2
tomli==2.0.1
toolz==0.12.0
tqdm==4.64.1
typing_extensions==4.4.0
urllib3==1.26.12
varint==1.0.2
vvm==0.1.0
vyper==0.3.7
wcwidth==0.2.5
web3==5.31.3
websockets==9.1
wrapt==1.14.1
yarl==1.8.1

What was wrong?

functions.owner().call() to TransparentUpgradeableProxy's implementation contract (i.e. a regular contract) works perfectly by returning address of owner.

functions.admin().call() to TransparentUpgradeableProxy contract is expected to return address of admin but failed miserably with the following issues:

  1. The admin().call() should NOT be reverted by EVM.
  2. The returned data in RPCResponse should not be None. Naturally, None object will not have "startswith" method.

Additional Information

  1. Since interaction codes are common for both contract addresses, logically there should not be any behavioral difference between call() made to TransparentUpgradeableProxy contract vs call() made to regular contract.

  2. Despite the code below is targeting Ethereum chain, I have experimented by switching the same code to BSC chain with TransparentUpgradeableProxy contract, issue no. 1 still persist. I am speculating the RPC HTTP endpoint is expecting different call_transaction in w3.eth.call() when interacting with TransparentUpgradeableProxy contract.

Please include any of the following that are applicable:

from web3 import Web3

def etherscan_common(token_address): chain_rpc_endpoint = "https://1rpc.io/eth" token_ens_address = Web3.to_checksum_address(token_address)

abi_endpoint = f'https://api.etherscan.io/api?module=contract&action=getabi&address={token_ens_address.lower()}'
response = requests.get(abi_endpoint)
response_json = response.json()
abi_in_json = json.loads(response_json['result'])

w3 = Web3(Web3.HTTPProvider(chain_rpc_endpoint))
token_proxy_or_impl_address = w3.eth.contract(address=token_ens_address, abi=abi_in_json)

function_identifiers = [
    function.function_identifier for function in token_proxy_or_impl_address.all_functions()]

if 'admin' in function_identifiers:
    admin_address = token_proxy_or_impl_address.functions.admin().call()
    print("admin_address: ", admin_address)
elif 'owner' in function_identifiers:
    owner_address = token_proxy_or_impl_address.functions.owner().call()
    print("owner_address: ", owner_address)

def etherscan_impl():

Etherscan TransparentUpgradeableProxy Implementation

token_address = "0x82354b770c4C09ea63E7d2eaC938a0DD2c0806F0"
etherscan_common(token_address)

def etherscan_proxy():

Etherscan TransparentUpgradeableProxy

token_address = "0x0955A73D014F0693aC7B53CFe77706dAb02b3ef9"
etherscan_common(token_address)

if name == "main": etherscan_impl()

# Workaround to enforce API rate limit of 1/5 sec, comment out if API_KEY is present
time.sleep(5.0)

etherscan_proxy()
* The full output of the error
```sh
D:\Issue\web3\venv\Scripts\python.exe D:\Issue\web3\call_to_TransparentUpgradeableProxy_reverted.py 
owner_address:  0x0000000000000000000000000000000000000000
Traceback (most recent call last):
  File "D:\Issue\web3\call_to_TransparentUpgradeableProxy_reverted.py", line 50, in <module>
    etherscan_proxy()
  File "D:\Issue\web3\call_to_TransparentUpgradeableProxy_reverted.py", line 41, in etherscan_proxy
    etherscan_common(token_address)
  File "D:\Issue\web3\call_to_TransparentUpgradeableProxy_reverted.py", line 25, in etherscan_common
    admin_address = token_proxy_or_impl_address.functions.admin().call()
  File "D:\Issue\web3\venv\lib\site-packages\web3\contract\contract.py", line 283, in call
    return call_contract_function(
  File "D:\Issue\web3\venv\lib\site-packages\web3\contract\utils.py", line 96, in call_contract_function
    return_data = w3.eth.call(
  File "D:\Issue\web3\venv\lib\site-packages\web3\eth\eth.py", line 256, in call
    return self._durin_call(transaction, block_identifier, state_override)
  File "D:\Issue\web3\venv\lib\site-packages\web3\eth\eth.py", line 275, in _durin_call
    return self._call(transaction, block_identifier, state_override)
  File "D:\Issue\web3\venv\lib\site-packages\web3\module.py", line 68, in caller
    result = w3.manager.request_blocking(
  File "D:\Issue\web3\venv\lib\site-packages\web3\manager.py", line 232, in request_blocking
    return self.formatted_response(
  File "D:\Issue\web3\venv\lib\site-packages\web3\manager.py", line 197, in formatted_response
    apply_error_formatters(error_formatters, response)
  File "D:\Issue\web3\venv\lib\site-packages\web3\manager.py", line 72, in apply_error_formatters
    formatted_resp = pipe(response, error_formatters)
  File "cytoolz\functoolz.pyx", line 666, in cytoolz.functoolz.pipe
  File "cytoolz\functoolz.pyx", line 641, in cytoolz.functoolz.c_pipe
  File "D:\Issue\web3\venv\lib\site-packages\web3\_utils\contract_error_handling.py", line 76, in raise_contract_logic_error_on_revert
    if data.startswith("Reverted "):
AttributeError: 'NoneType' object has no attribute 'startswith'

Process finished with exit code 1
kclowes commented 1 year ago

Are you the admin for the contract? It looks like admin call will revert unless msg.sender is the admin. From the TransparentUpgradeableProxy contract you linked on Etherscan:

  • NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.

I am curious to know the format of the response that you got back though so we can account for that case.

saninsteinn commented 1 year ago

+1 Sometimes requests to some arbitrum node also failed with this error.

PS

Have dug in my logs. Yep, the same node provider, but different chain: https://1rpc.io/arb

viper7882 commented 1 year ago

Good catch @kclowes. You are absolutely right where the contract is reverted due to ifAdmin modifier as I'm not the rightful admin. Unbelievable to me someone would have written the contract where only the owner could call and find out who the owner is. Nonetheless, it is still a miss from my end. My apology to you for calling it out as an issue.

However, the second issue remains. Using the codes above, if we print out response from contract_error_handling.py file, it should look similar to the following:

{'error': {'code': -32000, 'data': None, 'message': 'execution reverted'},
 'id': 1,
 'jsonrpc': '2.0'}

In my earlier run, I saw the id was having a value of 3 whereas the latest run is having a value of 1. I'm uncertain what does the meaning of the id is carrying but it seems changing from time to time.

According to JSON-RPC error codes: Errors with codes between -32000 and -32768 are reserved by the JSON-RPC 2.0 specification to indicate a general protocol exception.

Thank you in advance for looking into this issue for me.

kclowes commented 1 year ago

Thanks, that response helps. PR is up at #3054.