ERC-1967 存储槽:怎么在浏览器里看出一个 NFT 合约是代理壳子 图 1
ERC-1967 存储槽:怎么在浏览器里看出一个 NFT 合约是代理壳子 · 图 1

ERC-1967 存储槽:怎么在浏览器里看出一个 NFT 合约是代理壳子

一个地址为什么可能没有代码逻辑

在以太坊上,“合约地址”这个词容易给人一种错觉:地址后面跟着的字节就是这个合约的全部行为。代理合约打破了这种直觉。它自己只存一小段转发代码,真正的逻辑住在另一个地址上,靠 delegatecall 执行。关键细节在于执行位置:逻辑合约的代码被借用,但读写的是代理合约自己的存储。也就是说,所有权表、白名单、铸造计数这些状态全部记在壳子地址上,实现合约本身反倒可以一无所有。这意味着你在浏览器上看到的 NFT 合约,很可能只是一件空壳,而它的行为规则由”当前指向谁”决定。

规范约定的三个插槽

问题随之而来:工具怎么知道壳子指向哪份实现?各家项目曾把实现地址写在自己顺手的位置,浏览器只能一家一家写适配。ERC-1967 的做法是把位置固定下来,这份 EIP 在 2019 年 4 月 24 日创建,现在的状态是 Final。规范正文举了一个历史例子:OpenZeppelin 的合约把实现地址放在 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc,这个数不是随手取的,它由一个字符串的 keccak256 哈希减一得到,这样能保证编译器永远不会把普通状态变量分配到同一个槽上,代理和逻辑合约的存储不会撞车。规范还给出管理员槽(记录谁有权换实现)以及信标槽 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50,对应”许多代理共用一个信标合约”的形态。

规范特别提了一句值得留意:任何改动这些槽的函数都建议发出对应事件,包括从全零值的第一次初始化。原因是”能不能追查到谁在什么时候换了实现”决定了这套约定对持有人到底有多少价值。

持有人实际怎么查

第一步看字节码:如果地址上的代码是 ERC-1167 或 ERC-7511 那种四十余字节的固定样式,几乎可以确定是转发壳。第二步读实现槽,把该槽的值当作实现地址,浏览器上就能看到真正的 ABI 与源码。第三步找管理员:换实现的函数由谁调用、当前是否多签、是否延时生效,这些信息决定”你买到的规则是不是永久的”。第四步看事件历史:有没有已经发生的升级、升级前是否留出公告期。

这种结构意味着什么,不意味着什么

代理是双面的。好消息是它能修 bug:某个函数有缺陷时,官方部署一份新实现、让壳子改指向,持有人不需要搬资产。坏消息是它给了”改规则”一个技术入口:上限、白名单、转账开关、暂停能力,理论上都可能在实现层被改掉。链上代理本身既不说明项目可靠,也不说明项目能赖掉你手里的资产——代币编号和归属记在代理地址的存储里,换实现不会把 NFT 变走;但”这些 NFT 还能不能转、还能不能调用某些功能”取决于新实现。

工具视角的两个常见误读

第一种误读:在浏览器里看到”Contract: Proxy”标签就停止核查。标签只是工具对插槽读数的转述,正确姿势是点开实现地址读源码与写者记录,再回头找管理员地址是否在多签或时间锁后面。标签正确与否、实现地址是否公开源码、管理员是不是多签,是三件必须分开确认的事。

第二种误读:把”有代理”等同于”项目方可随意没收”。升级改变的是行为代码,不是所有权记录——你的代币编号与归属存在代理地址的存储里,换实现动不了它。真正需要评估的是升级后”还能不能正常转出、还能不能调用铸造与权益接口”这类功能层面的连续性。把这两层分开,就不会被”合约可升级所以是骗局”和”用了代理所以很先进”两种说法带偏。

所以看一个合集时,把”有代理”和”可升级”分开看,再把”可升级”和”谁有权升级”分开看,结论会清晰很多:真正需要盯的是管理员归属与事件历史,而不是”是不是代理”这个标签。本文为机制科普,不构成投资建议。