代码改变世界

web3.py使用笔记

2025-04-11 10:19  第二个卿老师  阅读(120)  评论(0)    收藏  举报

平时测试智能合约用到了web3.py,作为新手主要出问题的是根据abi文件构造交易这块,格式匹配不上,最近有时间,就结合自己经验总结下:

Solidity ABI类型 ↔ Python类型对照表

1. 基础类型

Solidity 类型 Python 类型 示例 & 注意事项
uint8 ~ uint256 int 123 → 123(需确保数值不超过类型范围,如 uint8 最大为 255)
int8 ~ int256 int -42 → -42
bool bool True/False → True/False
address ChecksumAddress 必须使用校验和地址:Web3.to_checksum_address("0xabc...")
bytes(动态长度) bytes 或 Hex字符串 b"hello""0x68656c6c6f"
bytes1~bytes32 bytes 或 Hex字符串 长度必须严格匹配:bytes2 → 2字节(如 b"\x01\x02""0x0102"
string str "Hello Web3" → "Hello Web3"

2. 数组类型

Solidity 类型 Python 类型 示例 & 注意事项
T[k](静态数组) list uint8[3] → [1, 2, 3](长度必须为 3)
T[](动态数组) list address[] → ["0xAbc...", "0xDef..."](支持空列表 []
T[][](嵌套数组) list of list uint256[][] → [[1, 2], [3]]

3. 复杂类型

Solidity 类型 Python 类型 示例 & 注意事项
结构体(Struct) dict 或 tuple { "id": 1, "name": "Alice" }(1, "Alice")(字段顺序必须与 ABI 一致)
元组(返回值) tuple 多返回值会自动转为元组:(123, "abc", True)
枚举(Enum) int 映射为整数,如 MyEnum.VALUE → 0(需手动转换语义)
定点数(fixed/ufixed) 手动处理 建议用字符串或整数(如 "123.45"12345,需自行处理精度)

关键注意事项

整数范围

  • uint8 最大值为 255,若传入 256 会报错。
  • Python 的 int 无大小限制,但需符合 Solidity 类型范围。

地址校验和

直接传递普通字符串地址会报错,必须转换:

address = Web3.to_checksum_address("0xabc...")

字节类型长度

bytes32 必须严格 32 字节:

# 正确写法(32字节 Hex)
my_bytes = "0x" + "00" * 32  # 全零的 bytes32

结构体字段顺序

如果用 tuple 传递结构体,字段顺序必须与 ABI 定义完全一致:

# Solidity struct { uint a; string b; }
args = (1, "test")  # 正确
args = ("test", 1)  # 错误!

实战示例

以下是我一个实践项目的合约abi与对应部分代码:

# 合约abi
  "abi": [
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "address",
              "name": "tokenAddress",
              "type": "address"
            },
            {
              "internalType": "uint256",
              "name": "tokenId",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "chainId",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "nftOwner",
              "type": "address"
            },
            {
              "internalType": "string",
              "name": "handle",
              "type": "string"
            },
            {
              "internalType": "string",
              "name": "imageURI",
              "type": "string"
            },
            {
              "internalType": "address",
              "name": "followModule",
              "type": "address"
            },
            {
              "internalType": "bytes",
              "name": "followModuleInitData",
              "type": "bytes"
            },
            {
              "internalType": "string",
              "name": "followNFTURI",
              "type": "string"
            },
            {
              "internalType": "address payable",
              "name": "inviter",
              "type": "address"
            }
          ],
          "internalType": "struct DataTypes.CreateProfileData[]",
          "name": "vars",
          "type": "tuple[]"
        }
      ],
      "name": "batchCreateAccount",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
  ]

##################################################
#################测试脚本#########################
from web3 import Web3

# 连接以太坊节点
w3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/v3"))
key4 = '53......575'
account = Account.from_key(private_key=key)
addr = account.address
# 定义合约
hbo_contract = w3.eth.contract(address=contract_addr, abi=abi)
# 合约交互方法
def buy_tba(key, num=1, invite_tba='0x0000000000000000000000000000000000000000', type='old'):
    create_profile_data = {}
    create_profile_data['tokenAddress'] = '0x0000000000000000000000000000000000000000'    # NFT合约地址
    create_profile_data['tokenId'] = 0  # NFT tokenID
    create_profile_data['chainId'] = 0  # 链ID
    owner_addr = w3.to_checksum_address('0xcf.....5')    # 代理地址
    create_profile_data['nftOwner'] = owner_addr  # nft所有者
    create_profile_data['handle'] = 'NFT_Name'  # 默认 NFT名#tokenId
    create_profile_data['imageURI'] = 'https://storage.xxx/files/my.jpg'
    create_profile_data['followModule'] = followModule   # 依赖合约地址
    create_profile_data['followModuleInitData'] = '0x'  # 默认传 []
    create_profile_data['followNFTURI'] = 'https://storage.xxx/files/my.jpg'
    create_profile_data['inviter'] = w3.to_checksum_address(invite_tba)  # 邀请者地址
    gas = 2028431 * num
    base_price = Decimal("0.006")
    if type == 'new':
        base_price = base_price / Decimal('2')
    tba_fee = base_price * Decimal(num)  # 价格
    data = [create_profile_data]
    print("data:", data)
    if num > 1:    # #创建多个
        for i in range(num-1):
            data.append(create_profile_data)
    try:
        hbo_txn = hbo_contract.functions.batchCreateAccount(data).build_transaction({
            'gas': gas,  #(1TBA/2000000000)
            'maxFeePerGas': Web3.to_wei('0.107000010499', 'gwei'),
            'maxPriorityFeePerGas': Web3.to_wei('0.107000010499', 'gwei'),
            'value': Web3.to_wei(tba_fee, 'ether'),
            'nonce': w3.eth.get_transaction_count(addr),
        })
        print("构造交易:", hbo_txn)
        signed_txn = w3.eth.account.sign_transaction(hbo_txn, private_key=key)
        print("签署交易", signed_txn)
        txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
        print("广播交易", w3.to_hex(txn_hash))
        w3.eth.wait_for_transaction_receipt(txn_hash)
        print("上链成功", w3.to_hex(txn_hash))
    except Exception as e:
        print(e)
        return False

从代码上看,入参构造算是比较麻烦的,其中部分打印如下:

data: [{'tokenAddress': '0x0000000000000000000000000000000000000000', 'tokenId': 0, 'chainId': 0, 'nftOwner': '0xcF0e1CAf97DD1F49f4f9eff3096fbebE0caeD975', 'handle': 'PoPP-Spacefarer', 'imageURI': 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', 'followModule': '0x63eeA5D273ce497FD2D4Ab464E835DfCdBbB', 'followModuleInitData': '0x', 'followNFTURI': 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', 'inviter': '0x0000000000000000000000000000000000000000'}, {'tokenAddress': '0x0000000000000000000000000000000000000000', 'tokenId': 0, 'chainId': 0, 'nftOwner': '0xcF0e1CAf97DD1F49f4f9eff3096fbebE0ca', 'handle': 'PoPP-Spacefarer', 'imageURI': 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', 'followModule': '0x63eeA5D273ce497FD2D4Ab464E835DfCdB', 'followModuleInitData': '0x', 'followNFTURI': 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', 'inviter': '0x0000000000000000000000000000000000000000'}]

构造交易 {'chainId': 167009, 'gas': 4056862, 'maxFeePerGas': 107000010, 'maxPriorityFeePerGas': 107000010, 'value': 12000000000000000, 'nonce': 217, 'to': '0x226EDbC009611a974D48D9bBB5745324855', 'data': '0xcb5e08b900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf0e1caf97dd1f49f4f9eff3096fbebe0caed9750000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000063eea5d273ce497fd2d4ab464e835dfcdbbb484f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f506f50502d537061636566617265720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003368747470733a2f2f73746f726167652e706f70702e636c75622f66696c65732f506f50502d537061636566617265722e6a7067000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003368747470733a2f2f73746f726167652e706f70702e636c75622f66696c65732f506f50502d537061636566617265722e6a706700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf0e1caf97dd1f49f4f9eff3096fbebe0caed9750000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000063eea5d273ce497fd2d4ab464e835dfcdbbb484f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f506f50502d537061636566617265720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003368747470733a2f2f73746f726167652e706f70702e636c75622f66696c65732f506f50502d537061636566617265722e6a7067000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003368747470733a2f2f73746f726167652e706f70702e636c75622f66696c65732f506f50502d537061636566617265722e6a706700000000000000000000000000'}

由上可知一些注意点:

  • 如0x地址需要补齐40个0,即"0x0000000000000000000000000000000000000000"
  • 正常地址要使用to_checksum_address方法
  • 关于bytes类型,这里填的是'0X',通常"0x"开头的16进制字符串,web3.py会自动将其识别为空bytes数据,当然也可以直接填写b''.
  • 最后有个vars参数,我起初以为要构造一个vars的字典,后面发现在ABI编码中,字段名称只是为了增强可读性,不参与底层编码。对于结构体参数,ABI编码是根据各个组件的顺序和类型进行编码的,所以这里传值用列表套列表也行,如下:
[['0x0000000000000000000000000000000000000000', 0, 0, '0xcF0e1CAf97DD1F49f4f9eff3096fbebE0caeD975', 'PoPP-Spacefarer', 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', '0x63eeA5D273ce497FD2D4Ab464E835DfC', b'aaaaa', 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', '0x0000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000', 0, 0, '0xcF0e1CAf97DD1F49f4f9eff3096', 'PoPP-Spacefarer', 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', '0x63eeA5D273ce497FD2D4Ab464E835Dfc', b'aaaaa', 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', '0x0000000000000000000000000000000000000000']]