如何判定 EVM 合约的类型

本文涉及的产品
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
可观测可视化 Grafana 版,10个用户账号 1个月
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 通过使用正确的API,可以轻松获取与合约地址相关的ERC20代币的所有转账记录。通过创建账户、编写使用API的脚本并使用getTokenTransfers函数,您可以访问和分析有关ERC20代币的有价值的转账数据。

作者:formatting

Web3链上数据常见的分析中,往往会有有大量判定 合约类型的需求,本文将从相关的标准以及 工程实践上,来对合约进行是否属于 ERC20 / ERC721 / ERC1155 几个合约的判定。
更多的 使用案例,可以查阅 [Chainbase] 的[开发者文档],或者通过 Discord原作者提问。我们很高兴能够与大家讨论 Web3 infraData SDK、Chainbase APIs 等相关的问题。

不同合约的判定规则

随着行业标准的完善,各个合约对应的 Functions 和 Events 都有详细的规定。所以,利用合约所支持的 Functions 进行类型判断,是非常高效和准确的方式。

下面表格内列出了一些ERC20 / ERC721 / ERC1155 必须支持的 Functions,可以给我们判定合约类型提供支持依据:

EIP-20 EIP-721 EIP-1155
allowance(address _owner, address _spender) approve(address _approved, uint256 _tokenId) balanceOf(address _owner, uint256 _id)
approve(address _spender, uint256 _value) balanceOf(address _owner) balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids)
balanceOf(address _owner) getApproved(uint256 _tokenId) isApprovedForAll(address _owner, address _operator)
decimals() isApprovedForAll(address _owner, address _operator) safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data)
name() ownerOf(uint256 _tokenId) safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data)
totalSupply() safeTransferFrom(address _from, address _to, uint256 _tokenId) setApprovalForAll(address _operator, bool _approved)
transfer(address _to, uint256 _value) safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) supportsInterface(bytes4 interfaceID)
transferFrom(address _from, address _to, uint256 _value) setApprovalForAll(address _operator, bool _approved)
symbol() transferFrom(address _from, address _to, uint256 _tokenId)
supportsInterface(bytes4 interfaceID)

当然,在工程实现和数据分析中,我们注意到链上存在大量特殊合约,比如:

  1. 早期合约:合约部署早于标准制定;
  2. 精简合约:未实现所有规定的方法;

它们虽然不能完全符合标准,但也拥有 Token approve / transfer 等一系列的 events,在实际操作中,也可以归为相关标准进行分析。

参考实例

基于以上的认知,Chainbase 的解决方案可以作为参考。我们的工程实现采用了一个更加精简,并充分验证了有效性的判定标准,如下所示:

Bytes Signature Functions For ERC20 Functions For ERC721 Functions For ERC1155
0x70a08231 balanceOf(address _owner) balanceOf(address _owner)
0xa22cb465 setApprovalForAll(address _operator, bool _approved) setApprovalForAll(address _operator, bool _approved)
0x00fdd58e balanceOf(address _owner, uint256 _id)
0x095ea7b3 approve(address,uint256)
0xa9059cbb transfer(address _to, uint256 _value)
0x42842e0e safeTransferFrom(address _from, address _to, uint256 _tokenId)
0xf242432a safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data)
0x2eb2c2d6 safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data)

如何工程实现?

工程上需要解决的问题是如何获取合约实现的方法列表,针对这个问题,我们采用了两种检测方式,各有优缺点,混合使用可以弥补相互的不足。

方法一:获取合约的 OpCode 生成 Functions Signature 列表

  • 优点:速度快
  • 缺点:需要寻找到背后的逻辑合约,对于复杂代理合约和非标准的代理合约的判定,存在一些偶发问题

需要注意:代理合约的处理方法

如果主合约是一个代理合约,我们需要进一步获取背后的逻辑合约地址,通过逻辑合约来获取合约所支持的 Functions。

下面是几种合约的具体处理方式:

EIP-1167

EIP-1167 是一个简单的的克隆合约,OpCode 以 363d3d373d3d3d363d73 开头,10-29 字节为主合约的地址

curl -X "POST" "<https://ethereum-mainnet.s.chainbase.online/v1/YOU_SECRET_KEY>" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "jsonrpc": "2.0",
  "method": "eth_getCode",
  "id": 1,
  "params": [
    "0xde400a2ed5a5f649a8cff6445a24ab934ff32b2c",
    "latest"
  ]
}'

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": "0x363d3d373d3d3d363d73e38f942db7a1b4213d6213f70c499b59287b01f15af43d82803e903d91602b57fd5bf3"
}

获取主合约地址:

fmt.Sprintf("0x%s", OP_CODE[20:60])

这样,我们就得到主合约地址: 0xe38f942db7a1b4213d6213f70c499b59287b01f1

EIP-1967

EIP-1967 对定了一系列的 storage slot 来存储一些代理合约的地址,例如:

  • Logic contract address storage: bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1))
  • Beacon contract address storage:bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1))

EIP-1967 规定了如果 Logic contract address storage 为空,则使用 Beacon contract address storage。所以我们可以通过判断合约是否存在这两个 slot 地址,来简单判断此合约是否是 EIP-1967 代理合约,再通过 eth_getStorageAt 获取背后的逻辑合约地址。

比如:[0x49542ad0f1429932e5b0590f17e676523f0a6369](https://www.notion.so/2022-11-28-12-5-f251c426fca34be78b435a8d3d7e4d29?pvs=21)

首先判断合约中是否存在两个 slot 地址:

// Logic contract address storage
strings.Contains(OP_CODE, "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
// Beacon contract address storage
strings.Contains(code, "a3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50")

再使用 eth_getStorageAt 获取 相关 slot 存储的主合约地址:

curl -X "POST" "<https://ethereum-mainnet.s.chainbase.online/v1/YOU_SECRET_KEY>" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "id": 1,
  "method": "eth_getStorageAt",
  "jsonrpc": "2.0",
  "params": [
    "0x49542ad0f1429932e5b0590f17e676523f0a6369",
    "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
    "latest"
  ]
}'

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": "0x0000000000000000000000007ad52eceffcb3bd41bc434a9acfecdbc8ef8450e"
}

最终获得主合约地址:0x7ad52eceffcb3bd41bc434a9acfecdbc8ef8450e

EIP-1967 EIP-1822 EIP-3561

这一系列的代理合约与 EIP-1967 类似,只是 storage slot 地址不一样,这里就不再赘述了。

与此类似,还有 openzeppelin 实现的一个可升级合约,使用的是 keccak256("org.zeppelinos.proxy.implementation") storage slot 地址。

漏网之鱼

需要注意的是,这些合约可能无法轻易获取到背后的逻辑合约,这就会导致我们无法准确地获取 Functions Signature 列表,比如:

  • EIP-2535 背后存在多个逻辑合约,每个逻辑合约可能只实现了一部分逻辑功能
  • 其他非标准的代理合约难以获取逻辑合约地址

所以,我们很需要第二种实现方式,来弥补方法一的不足:

方法二:利用 eth_call & eth_estimateGas 判断方法是否被定义

  • 优点:可以完成各类代理合约的判定
  • 缺点:需要多次调用 Chain RPC API 才能完成一次判定,而且依赖一些合约的错误处理,存在一定的误判率

使用 eth_call 调用可对只读方法进行检测,例如:balanceOf。

使用 eth_estimateGas 可对写入的方法进行检测, eth_estimateGas 需要合约抛出一些异常错误,否则可能会存在误判。

举几个使用 eth_call & eth_estimateGas 判断方法是否存在的示例:

1. Determine if the contract supports the balanceOf (address) method (0x70a08231)

curl -X "POST" "<https://ethereum-mainnet.s.chainbase.online/v1/YOU_SECRET_KEY>" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "jsonrpc": "2.0",
  "method": "eth_call",
  "id": 1,
  "params": [
    {
      "to": "0xb24cd494faE4C180A89975F1328Eab2a7D5d8f11",
      "data": "0x70a082310000000000000000000000000000000000000000000000000000000000000000"
    },
    "latest"
  ]
}'

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

2. setApprovalForAll(address operator, bool approved)

curl -X "POST" "<https://ethereum-mainnet.s.chainbase.online/v1/YOU_SECRET_KEY>" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "eth_estimateGas",
  "params": [
    {
      "to": "0x79fcdef22feed20eddacbb2587640e45491b757f",
      "data": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001"
    }
  ]
}'

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": "0xb647"
}

3. Function: safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes _data)

curl -X "POST" "<https://ethereum-mainnet.s.chainbase.online/v1/YOU_SECRET_KEY>" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "eth_estimateGas",
  "params": [
    {
      "to": "0x2079812353e2c9409a788fbf5f383fa62ad85be8",
      "data": "0xf242432a00000000000000000000000090bee68eb25db284d710a0805022a8a5d720a2860000000000000000000000008f7687d014c2655519fcac41fa990d2188310d776f16452bb8d1aa8d4f0b01d855b7d4ae7e803868000000000026290000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
    }
  ]
}'

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": null,
  "error": {
    "code": 3,
    "message": "execution reverted: ERC1155: caller is not owner nor approved"
  }
}

注意:如果上述方法不存在,我们通常会得到一个 -32000 的异常,通过此方式我们可以检测合约支持的方法列表。

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": null,
  "error": {
    "code": -32000,
    "message": "execution reverted"
  }
}

一个更简单的解决方式

当然,上面这些方法虽然已经经过了精简,但仍然显得很复杂,并且总是需要多次检验。

如果不想使用这么复杂的解析数据,也可以试试 [Chainbase] 提供的 Cloud API,能够非常简单高效地完成上面的判定,并且经过了多次的验证,能够将误判率降低到几乎不存在的程度。

目前我们已经完成了 Ethereum、Polygon、BSC、Fantom、Avalanche、Optimism、Arbitrum、Aptos、Sui、zkSync、Starknet、Base、Bitcoin 等多个链上数据的解析,可以直接通过 SQL API 轻松获取数据,不需要自己进行清洗和整理。

image.png

希望这篇文章能对正在对合约类型的分类感到困扰的朋友解决实际的操作问题,值得一提的是,这些分类往往是比较繁琐的,所以你可以尽可能使用一些工具,帮助你减少重复造轮子的工作,把精力集中到你的项目上来。

如果你有任何其他的想法,欢迎来到 [Chainbase] 的 Discord,和我直接讨论,另一个能找到我的方式在 twitter

感谢看完!

About Chainbase

Chainbase 是一个开放的 Web3 数据基础设施,用于大规模地访问、组织和分析链上数据。

Chainbase 通过一个数据平台,将丰富的数据集与开放的计算技术相结合,帮助人们更好地利用链上数据。Chainbase 的目标是让加密数据易于使用并发挥效益,使人人受益,不断帮助这个时代最有创造力的人们,实现他们的想法。

目前有超过 5,000 名开发人员在平台上进行构建,每天超过 200Mn 的后端数据请求,并将 Chainbase 集成到他们的主要工作流程中。此外,我们正在与 10+ 家头部公链进行合作,作为验证节点和提供商,非托管地管理着超过 💲500Mn 的代币。

原文链接: How to Determine the Type of an EVM Contract

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
高可用应用架构
欢迎来到“高可用应用架构”课程,本课程是“弹性计算Clouder系列认证“中的阶段四课程。本课程重点向您阐述了云服务器ECS的高可用部署方案,包含了弹性公网IP和负载均衡的概念及操作,通过本课程的学习您将了解在平时工作中,如何利用负载均衡和多台云服务器组建高可用应用架构,并通过弹性公网IP的方式对外提供稳定的互联网接入,使得您的网站更加稳定的同时可以接受更多人访问,掌握在阿里云上构建企业级大流量网站场景的方法。 学习完本课程后,您将能够: 理解高可用架构的含义并掌握基本实现方法 理解弹性公网IP的概念、功能以及应用场景 理解负载均衡的概念、功能以及应用场景 掌握网站高并发时如何处理的基本思路 完成多台Web服务器的负载均衡,从而实现高可用、高并发流量架构
目录
相关文章
|
存储 安全 区块链
OP链DAPP合约代币系统开发|详情方案|规则指南
智能合约和去中心化应用也面临着许多挑战
|
算法 JavaScript 前端开发
量化合约币安API自动交易策略程式开发源码规则部署
量化合约币安API自动交易策略程式开发源码规则部署
|
开发框架 前端开发 JavaScript
BSC链上进行智能合约开发部署规则详情
BSC(Binance Smart Chain)是一个基于区块链的智能合约平台,类似于以太坊。它提供了一个开发者友好的环境,可以用于创建和部署智能合约。如果你有一些编程经验,并且熟悉区块链和智能合约的基本概念,你可以按照以下步骤在BSC链上进行智能合约开发:
|
安全 JavaScript 前端开发
TRON波场链USDT合约开发源码规则部署
TRON波场链USDT合约开发源码规则部署
|
区块链
ERC20代币合约开发规则详解(源码示例)
ERC20代币合约是一个遵循ERC20标准的代币合约,它实现了代币转移、代币冻结、代币解锁等功能。作为一个通用的标准,ERC20代币合约可以被广泛应用于各种去中心化交易所、钱包、ICO等场景。
|
API 区块链
ERC20和BSC链调用智能合约转移拥有者权限
ERC20和BSC链调用智能合约转移拥有者权限
285 0
ERC20和BSC链调用智能合约转移拥有者权限
|
区块链
solidity 合约间调用以及参数传递
在 以太坊中合约间是可以相互调用,并且正常进行参数传递以及返回值处理. contract1.sol pragma solidity ^0.4.0; contract Test1 { uint256 public v=7; function vote(uint256 a) pu...
3367 0
|
JSON Java 区块链
在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑(3)
在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑(3)
253 0
在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑(3)
|
Java 区块链 开发工具
在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑(1)
在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑(1)
351 0
在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑(1)
|
Java Go 区块链
在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑(2)
在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑(2)
242 0