🧰 UtlKit

ABI 编码器怎么用: keccak256 出 4 字节选择器, 32 字节字, 偏移量与类型盲解码全在浏览器

对规范签名跑 keccak256 得到 4 字节选择器, 每个静态值变成一个 32 字节补零字, 动态值带从第一个头字量起的偏移量, 类型盲解码标签把原始字读回, 全部由浏览器内的 js-sha3 与 BigInt 计算.

ABI 编码器把 Solidity 函数签名和一组参数值变成可以直接粘进交易的 0x 开头 calldata, 而且全部在浏览器内完成. 没有后端: 页面内置 js-sha3 做 keccak256, 用原生 BigInt 拼 32 字节字, 用浏览器自带的 UTF-8 编码器给字符串定长. 两个标签页覆盖整个流程: 编码模式吃签名加参数值, 输出选择器加每个参数一个 32 字节字; 解码模式吃原始 calldata, 打印它能还原的内容. 签名输入框下方有实时选择器预览, 结果下方有 hex 字符数计数, 复制前就能看到大小. 先用 ABI 编码器工具 试一下, 输入规范的 transfer(address,uint256).

4 字节选择器是怎么算出来的

选择器是签名串原样跑 keccak256 得到的哈希的前 4 字节. keccak256 是 256 位哈希, 工具算出完整的 64 位 hex 摘要, 取前 8 个 hex 字符, 也就是 EIP-196 定义的合约方法分发前缀. 所以格式很重要: transfer(address,uint256) 哈希出大家熟悉的 0xa9059cbb, 但逗号后多一个空格的同一签名会哈希出完全不同的摘要, 前缀也跟着变, 调用就会打错函数或者直接 revert. 签名输入框下方的预览每敲一个键就重算一次, 拼写错误立刻可见. 想看完整摘要, Keccak 哈希工具 对同一输入会给出完整 64 hex 输出, 前 4 字节作为函数选择器, 前 20 字节作为地址形式.

静态值怎么变成 32 字节字

每个静态参数都写成一个 32 字节字, 右对齐前面补零. 地址必须正好 40 个 hex 字符, 可带可不带 0x 前缀, 补齐到 64. 布尔值接受 true, false, 1 或 0. bytes32 接受 1 到 64 个 hex 字符. 整数覆盖从 8 位类型到 uint256 的整个无符号家族, 以及对应的 128 位和 256 位有符号类型, 每个值在编码前先做范围检查: int128 必须落在负 2^127 到 2^127 减 1 之间, int256 落在负 2^255 到 2^255 减 1 之间. 超范围的输入会被拒绝, 不会静默回绕. 负整数用补码形式存储, 也就是值加 2^bits: stake(int256) 传 -1, 编码结果是选择器 0x77b71df0 后接一个由 64 个 f 组成的字, 这正是 32 字节里 -1 的样子.

动态值怎么拿到偏移量

string, bytes 和数组参数是动态的, 头槽位放的是偏移量而不是数据. 偏移量按字节从第一个头字量起, 就是紧跟 4 字节选择器后面的那个 32 字节字, 不是从选择器本身量起. 只有一个动态参数时, 数据从第一个尾字开始, 比头槽起点晚 32 字节, 所以偏移量字就是 32. 尾部先是长度字, 然后是实际数据, 补齐到 32 字节的整数倍. 长度按 UTF-8 字节数算, 不是字符数, 一个 emoji 占 4 字节. greet(string) 传 hi, 得到选择器 0xead710c4, 偏移量字 32, 长度字 2, 数据字以 0x6869 开头, 即 h 和 i 的 hex, 后面补零.

一次完整编码, 逐字拆解

用 transfer(address,uint256) 拼一次, 接收方 0x1234567890123456789012345678901234567890, 金额 100. 结果是 0xa9059cbb00000000000000000000000012345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000000064, 计数显示 136 个 hex 字符, 正好是选择器 8 加地址字 64 加金额字 64. 金额 100 右对齐在最后那个字里, 100 的 hex 是 64, 地址的 48 个 hex 字符坐在自己那个字的末尾, 前面 24 个零.

解码标签页能做什么, 不能做什么

解码标签页去掉 0x 前缀, 取前 8 个 hex 字符作为选择器, 之后每 64 个 hex 字符按无符号 256 位整数读成一个十进制数. 喂进 stake(uint256,uint256) 的 calldata, 它报告选择器 0x7b0472f0 加两个参数 100 和 2026, 干净地往返. 解码器是类型盲的: 没有 ABI, 没有类型表, 所以 transfer 的 calldata 会把接收方打印成 48 位十进制 103929005307130220006098923584552504982110632080, 而不是地址; string 参数回来的是原始的偏移量, 长度和数据字, 不是文本. 需要看某个字背后的字节时, 把 hex 部分粘进 Hex 转换工具 就能读回 UTF-8 文本.

这个工具不是什么

ABI 编码器不是带类型的合约浏览器: 它只编解码原始字, 想上链查交易, 地址或代币, 用 区块浏览器工具 直连 Etherscan, BscScan, PolygonScan 和主流 L2 浏览器. 它也不是地址校验器: 接收方那个字是 32 字节, 地址是那个字末尾的 40 个 hex 字符, 而 ETH 地址检查工具 会把地址重排成 EIP-55 混合大小写并标出校验和不符. 它也不是手续费计算器: calldata 长度会影响 gas 消耗, Gas 费用估算器 把 gas 上限加基础费和优先费换算成 ETH 和美元成本. 它也不是数值的进制转换器: 小数值用 进制转换工具 就能验证十进制转 hex, 比如 100 变 64, 但它只支持 32 位范围. 它还不是字节级文本工具: 长度字按 UTF-8 字节算, 想看文本逐字符怎么展开, 文本转二进制指南 讲了 ASCII 输入的 8 位展开.

相关工具

常见问题

ABI 编码器会调用任何 API 或服务器吗?

不会. 页面内置 js-sha3, 在本地跑 keccak256, 用 BigInt 拼每个 32 字节字, 用浏览器自带的 UTF-8 编码器给字符串定长. 除了可选的反馈表单, 你输入的任何内容都不会离开页面.

为什么多一个空格就会改变选择器?

keccak256 是对签名串原样计算的. 规范形式在逗号后没有空格, 被哈希的就是那个确切的字节序列. 多一个空格, 哈希的就是另一个串, 摘要不同, 前 8 位 hex 前缀也不同, 调用就会打错函数或者直接 revert. 实时预览就是让你在复制前发现这类错误.

为什么解码出来的地址字会打印成一个长十进制数?

解码器是类型盲的. 它没有 ABI, 也没有类型表, 所以把每个 64 hex 字块都读成无符号 256 位整数的十进制. 地址是那个 32 字节字末尾的 40 个 hex 字符, 需要把这个值交给别的工具时, 从 hex 出发, 不要用打印出来的十进制.

为什么第一个字符串偏移量是 32 而不是 4?

偏移量按字节从第一个头字量起, 就是紧跟 4 字节选择器后面的那个 32 字节字, 不是从选择器本身量起. 只有一个动态参数时, 数据从第一个尾字开始, 比头槽起点晚 32 字节, 所以偏移量字是 32. greet(string) 传 hi, 结果是选择器 0xead710c4, 偏移量 32, 长度 2, 数据字以 0x6869 开头并补齐到 32 字节.

为什么计数器报告 hex 字符数而不是字节数?

每个字节变成 2 个 hex 字符, 所以计数器显示 136 意味着线上 68 字节, 即 4 字节选择器加两个 32 字节字. 工具显示 hex 字符数, 因为那是你复制的形式, 而且让字的结构一目了然: 每加一个静态值, 字符串就变长 64 个字符, 选择器再贡献 8 个.

相关文章