什么是ABI
在以太坊中,ABI(Application Binary Interface) 是智能合约与外部世界(如 Web3 应用或 DApp)进行交互的接口。它定义了智能合约的函数、事件、数据结构的编码规则,并且通过 ABI,你可以从外部调用合约的函数,或监听合约事件。
一个智能合约的 ABI 通常是一个 JSON 文件,包含了合约中所有可调用的函数、可监听的事件及其参数的描述。了解 ABI 对于与智能合约交互非常重要,下面详细解释 ABI 的各个部分。
1. ABI 的结构
ABI 是一个包含对象的数组,每个对象定义了一个函数、构造函数或事件。每个对象包含的信息因类型不同而略有差异。常见类型包括 function
、constructor
和 event
。
[
{
"constant": false,
"inputs": [{"name": "recipient", "type": "address"}, {"name": "amount", "type": "uint256"}],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{"name": "_initialSupply", "type": "uint256"}],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [{"indexed": true, "name": "from", "type": "address"}, {"indexed": true, "name": "to", "type": "address"}, {"indexed": false, "name": "value", "type": "uint256"}],
"name": "Transfer",
"type": "event"
}
]
2. 函数对象(function
)
ABI 中最常见的是 function
类型的对象。它表示合约中的某个函数,并描述如何调用该函数以及如何解析其返回值。
属性说明:
name
:函数的名称,如transfer
。inputs
:函数的输入参数列表,参数包含name
和type
属性。name
是参数名称,type
是参数类型(如address
,uint256
等)。outputs
:函数的返回值描述,通常是一个数组,包含每个返回值的类型和名称(名称可以为空)。constant
(过时):指明该函数是否为只读函数,不改变区块链状态。该字段已被stateMutability
取代。stateMutability
:定义函数的状态可变性。可能的值有:pure
:函数不读取也不改变链上状态。view
:函数可以读取链上状态但不能修改。nonpayable
:函数可以修改链上状态,但不能接受以太币。payable
:函数可以接受以太币,并且可能会修改链上状态。
payable
(过时):该字段表示函数是否可以接收以太币。
示例
json
复制代码
{ "constant": false, "inputs": [{"name": "recipient", "type": "address"}, {"name": "amount", "type": "uint256"}], "name": "transfer", "outputs": [{"name": "", "type": "bool"}], "payable": false, "stateMutability": "nonpayable", "type": "function" }
name
:transfer
是函数名。inputs
:表示这个函数接受两个输入参数:recipient
是一个address
类型,amount
是一个uint256
类型。outputs
:函数返回一个bool
类型的值,表示转账是否成功。stateMutability
:nonpayable
,表示这个函数不能接收以太币。
3. 构造函数对象(constructor
)
构造函数是合约部署时调用的函数。它用于初始化合约,通常设置初始状态。constructor
对象在 ABI 中没有 name
属性,因为构造函数没有名称。
属性说明:
inputs
:输入参数列表。payable
:指明构造函数是否接受以太币。已被stateMutability
取代。stateMutability
:描述该构造函数是否允许接收以太币(同函数的stateMutability
)。type
:构造函数的类型,固定为constructor
。
示例
{
"inputs": [{"name": "_initialSupply", "type": "uint256"}],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
}
- 该合约的构造函数接受一个
uint256
类型的参数_initialSupply
,表示初始代币供应量。 stateMutability
为nonpayable
,表示构造函数不能接收以太币。
4. 事件对象(event
)
事件是合约中的一种日志记录机制,可以在合约外部通过 web3.js
或 web3.py
等库来监听事件的触发。事件允许从合约中发送信号,通知外部某些状态的变化。
属性说明:
name
:事件名称。inputs
:事件的输入参数列表。与函数参数类似,但事件的输入参数有一个额外的属性indexed
,它允许对某些参数进行索引,以便更容易地通过区块链日志查询。anonymous
:表示该事件是否是匿名的,匿名事件不记录事件的签名。默认为false
。type
:事件的类型,固定为event
。
示例
{
"anonymous": false,
"inputs": [
{"indexed": true, "name": "from", "type": "address"},
{"indexed": true, "name": "to", "type": "address"},
{"indexed": false, "name": "value", "type": "uint256"}
],
"name": "Transfer",
"type": "event"
}
name
:Transfer
是事件的名称,通常表示 ERC-20 代币转移。inputs
:有三个输入参数:from
和to
是address
类型,且为indexed
,表示这些参数可以用于事件过滤。value
是uint256
类型,表示转移的代币数量。
anonymous
:false
,表示该事件不是匿名事件。
5. 状态变量对象(state
)
有时 ABI 也会包含合约的状态变量,这些变量可用于查询合约的当前状态。状态变量可以是合约的公开属性。
示例
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [{"name": "", "type": "uint256"}],
"payable": false,
"stateMutability": "view",
"type": "function"
}
- 该状态变量
totalSupply
是公开的,返回一个uint256
类型的值,表示当前代币的总供应量。 - 由于这是一个只读操作,因此
stateMutability
设置为view
。
6. 调用智能合约的 ABI 示例(Python web3.py
)
你可以使用 web3.py
库来加载 ABI 并调用合约方法。以下是如何使用 ABI 调用合约函数的示例:
from web3 import Web3
# 连接到以太坊网络
w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID'))
# 合约 ABI 和地址
contract_abi = [
{
"constant": False,
"inputs": [{"name": "recipient", "type": "address"}, {"name": "amount", "type": "uint256"}],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"payable": False,
"stateMutability": "nonpayable",
"type": "function"
}
]
contract_address = '0xContractAddress'
# 加载合约
contract = w3.eth.contract(address=contract_address, abi=contract_abi)
# 调用合约方法
tx_hash = contract.functions.transfer('0xRecipientAddress', 1000).transact({'from': '0xYourAddress'})
print(f"Transaction hash: {w3.toHex(tx_hash)}")
7. ABI 类型说明
ABI 中最常见的参数类型包括:
address
:以太坊地址(20 字节)。uint256
:256 位无符号整数。int256
:256 位有符号整数。bool
:布尔类型(true
或false
)。bytes
:字节数组,固定长度或可变长度。string
:字符串。array
:数组类型,形如uint256[]
。
ABI 是智能合约与外部交互的桥梁,它详细描述了合约的函数、事件和状态变量的编码方式。通过 ABI,外部应用可以准确地调用合约的函数或监听事件。理解 ABI 的结构及其组成部分,是成功与智能合约进行交互的关键。
通过 Etherscan API 获取智能合约的 ABI
Etherscan 提供了一个 API 接口,可以根据合约地址获取已部署合约的 ABI。你需要申请一个 Etherscan API Key。
步骤:
- 注册 Etherscan 并获取 API Key:Etherscan API
- 使用 API 获取合约的 ABI。
示例代码:
import requests
from web3 import Web3
# Etherscan API key 和智能合约地址
etherscan_api_key = 'YourEtherscanAPIKey'
contract_address = '0xContractAddress'
# 通过 Etherscan API 查询合约 ABI
etherscan_api_url = f'https://api.etherscan.io/api?module=contract&action=getabi&address={contract_address}&apikey={etherscan_api_key}'
response = requests.get(etherscan_api_url)
contract_abi = response.json()['result']
# 将 ABI 从 JSON 格式解析成 Python 可读格式
contract_abi = eval(contract_abi)
# 连接到以太坊网络
w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID'))
# 加载智能合约
contract = w3.eth.contract(address=contract_address, abi=contract_abi)
# 查询合约中的数据(以 ERC-20 代币的 `balanceOf` 方法为例)
balance = contract.functions.balanceOf('0xSomeAddress').call()
print(f"Balance: {balance}")
# 调用合约的方法,例如进行代币转账
tx_hash = contract.functions.transfer('0xRecipientAddress', 1000).transact({'from': '0xYourAddress'})
print(f"Transaction hash: {w3.toHex(tx_hash)}")
3. 解析并调用智能合约方法
无论是通过手动加载 ABI 还是通过 Etherscan 获取 ABI,你都可以调用合约的任何方法。这里详细说明如何解析 ABI 并调用合约的方法:
读取 ABI
智能合约的 ABI 是一个 JSON 格式的数组,包含合约中可调用的所有方法和事件。你可以使用 Python 的 eval()
方法将 ABI 字符串解析成 Python 字典格式。
contract_abi = eval(abi_from_etherscan) # 将字符串解析为 Python 数据类型
加载智能合约
加载合约时,使用 web3.eth.contract()
方法,并传入合约地址和 ABI:
contract = w3.eth.contract(address=contract_address, abi=contract_abi)
调用合约的只读方法
合约中的函数可以分为读方法(不修改链上状态)和写方法(会修改链上状态)。读取合约数据时,使用 .call()
方法。
balance = contract.functions.balanceOf('0xSomeAddress').call()
print(f"Balance: {balance}")
调用合约的写方法
如果调用的是会修改链上状态的方法,例如转账代币,需要用 .transact()
方法,并传递发送者地址及其他必要的交易信息。
tx_hash = contract.functions.transfer('0xRecipientAddress', 1000).transact({'from': '0xYourAddress'})
print(f"Transaction hash: {w3.toHex(tx_hash)}")
监听合约事件
你还可以监听合约的事件,例如 ERC-20 代币的 Transfer
事件:
# 创建事件过滤器
transfer_filter = contract.events.Transfer.createFilter(fromBlock='latest')
# 监听事件
while True:
for event in transfer_filter.get_new_entries():
print(f"New Transfer event: {event}")