I’ve been working this week to replace the Brownie-specific sections of the degenbot code base. This commit was the first big set of changes, and I’ve made several refinements since then.
I’ve learned a few things about web3.py along the way. First, I highly recommend reading the official documentation Migrating your code from v5 to v6 page.
It is mostly straightforward, but there are some subtle differences that you should be aware of.
get_transaction Return Values
A subtle difference to be sure, but version v5 and v6 have differing output from the get_transaction
method.
In v5, the input
and data
fields are returned as a hex-formatted string. In v6, they are HexBytes
, which is a wrapped bytes object that displays as hex by default. Be careful when directly comparing a hex string with a HexBytes
object, since they don’t behave like you’d expect!
If you need to do string comparisons or provide a string input to a function, but you have a HexBytes
object, convert it with the hex()
method.
>>> HexBytes('0x069420')=='0x069420'
False
>>> HexBytes('0x069420').hex()=='0x069420'
True
Type and Chain ID
In v5, the transactio fields 'type'
and 'chainID'
are presented as hex-formatted strings. In v6, they are converted to integers. If you’re doing a check like this:
if transaction["type"] == '0x0':
do_something()
Please update it to:
if transaction["type"] == 0:
do_something()
Or make it compatible with both versions by accepting both types:
if transaction["type"] in (0, '0x0'):
do_something()
Or doing an inline conversion in the comparison:
if int(transaction["type"]) == 0:
do_something()
Function Naming
Web3.py version 5 used a mixture of snake_case
and camelCase
for function names. The most commonly used camel case functions in degenbot were isConnected
and toChecksumAddress
.
Checksum Addresses
After reading the code behind web3.py’s toChecksumAddress
and to_checksum_address
, it became clear that web3.py is just wrapping a function provided by the eth-utils module. It’s quite simple to just replace the use of Web3.toChecksumAddress
with eth_utils.to_checksum_address
, and I’ve done this throughout the code base.
If you are checksumming addresses inside your scripts, be sure to make this change.
Brownie handles both lower-case and checksummed addresses automatically, but web3.py will throw warnings and exceptions if you attempt to use a lower-case address without special arguments. Addresses within degenbot functions and methods are checksummed before use, but you may run into issues if you do comparisons in your client code without considering this.
Workaround
My goal is to support web3.py v5 until the transition away from Brownie can be made complete. To do this effectively, I need to handle both versions of web3.py by default.
Python allows you to retrieve an attribute of a particular object with the getattr
function. Since everything in Python is an object, I can quickly identify which version of web3.py the script has loaded by calling getattr
on the module name.
For example, if I want to use the isConnected
(or is_connected
) method on a Web3
object, I can check for both attributes:
for method_name in ("is_connected", "isConnected"):
try:
connected_method = getattr(w3, method_name)
except AttributeError:
pass
else:
break
And now I have a reference to the method that I can use directly:
if connected_method() == False:
raise ValueError("Web3 object is not connected.")
I use this technique in a few places in the code base. I’ve ordered the method names so that the v6 method appears first, since this the preferred condition.
Which Functions Are Affected?
This may not be an exhaustive list, but I’ve identified these:
createFilter
→create_filter
fromWeb3
→from_web3
getLogs
→getlogs
isConnected
→is_connected
isChecksumAddress
→is_checksum_address
processReceipt
→process_receipt
processLog
→process_log
solidityKeccak
→solidity_keccak
toAddress
→to_address
toBytes
→to_bytes
toChecksumAddress
→to_checksum_address
toHex
→to_hex
toInt
→to_int
toJSON
→to_json
toText
→to_text
Here are some Github links that include more detail on these changes:
Changes to decode_function_input
This one tripped me up for a while. The documentation for v5 says that decode_function_input returns a dictionary of values for the function parameters, however I’ve confirmed that sometimes it returns a tuple instead.
Here’s an example of a nested multicall, decoded from a transaction I wrote about in Part IV of the Transaction Prediction series. Skipping some steps required to unwrap the multiple encodings, I decode the final payload with web3.py v5: