以太坊 vs Polkadot 预编译合约对比 | 同样的入口,完全不同的能力边界
原文作者:PaperMoon 团队
以太坊开发者对预编译合约不陌生——`ecrecover` 在 `0x01`,`sha256` 在 `0x02`,用了很多年。Polkadot 也有预编译,调用方式完全一样:构造一个 call,目标地址填预编译地址,传入参数,拿回结果。
但当你把两边的预编译清单摆在一起,差异就很明显了。以太坊的预编译像一个密码学工具箱——哈希、签名、椭圆曲线运算,全是底层数学原语。Polkadot 的预编译更像一个系统调用接口——跨链消息、原生资产、底层存储、账户转换,直接连通链的核心功能。
这不是"谁多谁少"的问题,是设计哲学不一样。
一、先看清单:两边各有什么
以太坊预编译(14 个,截至 Prague 升级)
| 地址 | 名称 | 引入时间 | 功能 |
|---|---|---|---|
| 0x01 | ECRECOVER | Frontier(2015) | 从 ECDSA 签名恢复签名者地址 |
| 0x02 | SHA2-256 | Frontier | SHA-256 哈希 |
| 0x03 | RIPEMD-160 | Frontier | RIPEMD-160 哈希 |
| 0x04 | IDENTITY | Frontier | 原样返回输入数据(内存复制) |
| 0x05 | MODEXP | Byzantium(2017) | 大整数模幂运算 |
| 0x06 | ECADD | Byzantium | alt_bn128 椭圆曲线点加 |
| 0x07 | ECMUL | Byzantium | alt_bn128 椭圆曲线标量乘 |
| 0x08 | ECPAIRING | Byzantium | alt_bn128 配对检查 |
| 0x09 | BLAKE2F | Istanbul(2019) | BLAKE2b 压缩函数 |
| 0x0A | KZG_POINT_EVAL | Cancun(2024) | EIP-4844 blob 承诺验证 |
| 0x0B-0x0E | BLS12-381 系列 | Prague(2025) | BLS 签名相关的 G1/G2 点运算 |
14 个预编译,11 个是密码学运算,2 个是椭圆曲线配对/验证,1 个是内存复制工具函数。
Polkadot 预编译(4 大类)
| 类型 | 地址 | 核心能力 |
|---|---|---|
| ERC20 | 按 Asset ID 动态生成 | 操作链上原生资产(USDT、USDC 等) |
| System | 0x...0900 | BLAKE2 哈希、SR25519 签名验证、地址转换、合约终止、剩余 Weight 查询 |
| Storage | 0x...0901 | 底层存储的键值读写、部分读取、存储检查 |
| XCM | 0x...0a0000 | 跨链消息发送、本地执行、费用预估 |
数量看起来少,但每一类包含多个函数,实际覆盖的功能面比以太坊宽得多。
二、密码学:重叠不多,各有侧重
这是两边唯一有交集的领域。
哈希算法
| 算法 | 以太坊 | Polkadot |
|---|---|---|
| SHA-256 | 有(0x02) | 没有预编译,可用 Solidity 库 |
| RIPEMD-160 | 有(0x03) | 没有 |
| BLAKE2b | 有(0x09,压缩函数) | 有(BLAKE2-256 + BLAKE2-128,完整哈希) |
| Keccak-256 | 是 EVM 原生操作码,不需要预编译 | 同上 |
以太坊的 BLAKE2 预编译(0x09)只暴露了压缩函数(BLAKE2F),你想算一个完整的 BLAKE2b 哈希还得自己在合约里拼装调用。Polkadot 的 System 预编译直接给了 `hashBlake256` 和 `hashBlake128` 两个完整的哈希函数——传入数据,拿回哈希值,不需要自己管轮次和状态。
BLAKE2 是 Substrate 的默认哈希算法,所以 Polkadot 把它做成一等公民是合理的。以太坊生态更多用 Keccak-256(已是操作码级别),BLAKE2 只在特定场景(比如 Zcash 验证)才用到。
签名验证
| 签名方案 | 以太坊 | Polkadot |
|---|---|---|
| ECDSA (secp256k1) | ECRECOVER(0x01) | ecdsaToEthAddress(System 预编译) |
| SR25519 | 没有 | sr25519Verify(System 预编译) |
| BLS12-381 | 有(0x0B-0x0E,Prague 新增) | 没有预编译 |
| Ed25519 | 没有预编译 | 没有预编译(但 Substrate 原生支持) |
这里的差异很能说明问题。
以太坊的 ECRECOVER 是最早的预编译之一,因为 secp256k1 签名恢复是以太坊交易验证的基础。没有它,合约里就没法验证"这条消息真的是某个地址签的"。2025 年 Prague 升级又加了 BLS12-381 系列,为的是支持信标链验证者签名的链上验证。
Polkadot 这边,`sr25519Verify` 是独有的。SR25519 是 Polkadot 原生钱包(Polkadot.js)的默认签名算法,以太坊生态不用这个。有了这个预编译,你的 Solidity 合约能直接验证 Polkadot 原生钱包签的消息——这在"以太坊合约需要跟 Polkadot 原生账户交互"的场景下非常关键。
以太坊没有 SR25519,Polkadot 没有 BLS12-381。各自补的是自己生态最需要的那块。
三、以太坊完全没有的东西:资产、存储、跨链
从这里开始,两边不在一个维度上了。
ERC20 预编译:合约直接操作链上原生资产
以太坊上,USDT、USDC 是**部署在链上的 ERC20 合约**。你想在自己的合约里操作 USDT,得 `import` 它的接口,然后 `call` 它的合约地址。USDT 的余额、转账逻辑、授权机制——全在 Tether 部署的那个合约里。
Polkadot 上,USDT、USDC 是 **Assets pallet 管理的原生资产**——不是一个 ERC20 合约,而是写在链的 runtime 里的资产。ERC20 预编译的作用是:给这些原生资产套上一层标准 ERC20 接口,让 Solidity 合约可以用 `transfer()`、`approve()`、`balanceOf()` 这些熟悉的函数来操作它们。
```solidity // 在 Polkadot 上用 Solidity 操作原生 USDT // On Polkadot, interact with native USDT using Solidity IERC20 usdt = IERC20(0x000007C000000000000000000000000001200000); uint256 balance = usdt.balanceOf(msg.sender); usdt.transfer(recipient, amount); ```每个原生资产有一个唯一的 Asset ID,预编译地址根据 Asset ID 动态计算。不是一个固定地址,而是一个地址空间——有多少原生资产,就有多少预编译地址。
这意味着什么?在以太坊上,一个 ERC20 代币的转账要经过两层合约调用(你的合约 → ERC20 合约)。在 Polkadot 上,ERC20 预编译直接调用 Assets pallet 的原生逻辑,少了一层合约间跳转,执行效率更高。
但也有代价:`name()`、`symbol()`、`decimals()` 这三个 ERC20 元数据函数没有实现。如果你的前端依赖这些函数来显示代币信息,需要另想办法。
Storage 预编译:合约直接读写底层存储
以太坊的合约存储模型很简单:每个合约有自己的 256 位 key-value 存储空间,通过 SSTORE/SLOAD 操作码读写。合约只能访问自己的存储。
Polkadot 的 Storage 预编译(`0x...0901`)打开了一扇不一样的门:
```solidity // 精确读取:只取存储值的某一段,不用把整个值加载到内存 // Partial read: fetch only a segment of the stored value function get_range(bytes32 key, uint32 offset, uint32 length) external view returns (bytes memory); // 存储前先检查 key 是否存在,避免读不存在的 key 导致 revert // Check key existence before reading to avoid revert function has_key(bytes32 key) external view returns (bool); // 读取并删除:一步完成"取出来然后清掉" // Read and remove in one step function takeStorage(uint32 flags, bool isFixedKey, bytes memory key) external; ````get_range` 是最实用的:如果一个 key 下存了 10KB 的数据,你只需要其中 200 字节,在以太坊上你得把整个 10KB 加载进 EVM 内存再截取。在 Polkadot 上,`get_range` 直接从底层取那 200 字节,省 Gas(准确说是省 ref_time 和 proof_size)。
以太坊没有这种精细度的存储操作预编译。EVM 的存储模型是"一个 slot 固定 32 字节",虽然简单但不灵活。
XCM 预编译:合约里直接发跨链消息
这是差距最大的地方。
以太坊没有跨链预编译。想在以太坊合约里发一条消息给另一条链,你得接入第三方桥协议(LayerZero、Wormhole、Axelar),每个桥有自己的接口、自己的信任假设、自己的手续费结构。没有标准。
Polkadot 的 XCM 预编译直接把跨链通信变成了三个函数调用:
```solidity // 在本链执行一条 XCM 消息 // Execute an XCM message locally function execute(bytes calldata message, Weight calldata weight) external; // 发送 XCM 消息到另一条平行链 // Send an XCM message to another parachain function send(bytes calldata destination, bytes calldata message) external; // 预估执行一条 XCM 消息需要多少资源 // Estimate weight required for an XCM message function weighMessage(bytes calldata message) external view returns (Weight memory); ```你的 Solidity 合约可以直接往另一条平行链发消息——转资产、触发远程合约调用、查询跨链状态。不需要桥,不需要中间人,走的是 Polkadot 原生的跨共识消息协议。
当然,XCM 消息需要用 SCALE 编码(Polkadot 的原生序列化格式),对以太坊开发者来说是个额外的学习成本。而且当前的 XCM 预编译提供的是"裸接口"——`execute` 和 `send` 需要你自己构造完整的 XCM 指令序列,没有封装好的高级函数。实际用起来,大概率需要一层 Solidity wrapper 来简化操作。
四、设计哲学:工具箱 vs 系统调用
把两边的差异抽象一层来看:
以太坊的预编译是"补全 EVM 能力短板"的工具箱。** EVM 本身是一个通用计算环境,密码学运算如果用纯 Solidity 写会极其昂贵(椭圆曲线运算可能消耗数百万 Gas),所以把最常用的密码学原语做成预编译,降低开销。9 年来,每次硬分叉加几个新的密码学函数——BN128 配对(为 zk-SNARKs 服务)、BLAKE2(为 Zcash 互操作服务)、BLS12-381(为信标链服务)、KZG(为 blob 服务)。需求驱动,逐个补丁。
Polkadot 的预编译是"把链的原生能力暴露给合约"的系统调用接口。** Polkadot 的智能合约不是链的核心功能,而是通过 pallet_revive 后加的一层。链本身的能力——资产管理、存储系统、跨链通信、密码学验证——都已经存在于 runtime 里。预编译做的事情是在 PVM 和 runtime 之间架一座桥,让 Solidity 合约能"打电话"给这些原生功能。
这解释了一个有趣的反差:以太坊有 14 个预编译但几乎全是密码学,Polkadot 只有 4 大类但覆盖了资产、存储、跨链。因为以太坊的预编译在"补底层能力",Polkadot 的预编译在"开上层接口"。
五、对迁移开发者的实际影响
如果你正在把以太坊合约迁移到 Polkadot,以下几点跟预编译直接相关:
你熟悉的以太坊预编译还在。 ECRECOVER、SHA-256 这些以太坊原生预编译在 Polkadot 的 EVM 兼容层中仍然可用。依赖这些预编译的合约不需要修改。
ERC20 代币交互逻辑可能要调整。如果你的合约在以太坊上通过合约地址调用 USDT/USDC,迁移到 Polkadot 后需要改成调用对应的预编译地址。接口一样(还是 `transfer`、`approve`),但地址不同。
**跨链功能是新增项。** 以太坊合约里没有跨链预编译这个概念。如果你想利用 Polkadot 的跨链能力,XCM 预编译是全新的东西,需要学习 XCM 指令集和 SCALE 编码。
**存储优化有新手段。** Storage 预编译的 `get_range` 让你能做以太坊上做不到的部分存储读取。如果你的合约有大量存储操作,这是一个值得探索的优化方向。
**`weightLeft()` 替代了 `gasleft()`。** 以太坊里用 `gasleft()` 检查剩余 Gas。Polkadot 的 System 预编译提供 `weightLeft()`,返回的是 `refTime` 和 `proofSize` 两个值——对应 Polkadot 的三维资源计量模型。如果你的合约里有基于 `gasleft()` 的逻辑(比如在 Gas 不足时提前退出),需要适配成两个维度的判断。
小结
两套预编译体系的差异,本质上反映的是两条链对"智能合约该做多少事"这个问题的不同回答。以太坊认为合约是链的核心,所以给合约补密码学工具让它自己搞定一切。Polkadot 认为合约是链的一部分,链本身有很多现成的能力,合约只需要有一条通道调过去就行。哪种设计更好没有标准答案,但对开发者来说,理解这个差异比记住地址表有用得多。
