跳到正文
fevou.独立加密知识站

代币合约可升级是什么意思:谁能换掉代码、换过几次怎么查

今天查到的不能增发、没有卖出税,只对当前这一版实现合约成立。先认出代理,再找能按升级键的人,最后翻升级记录。

图中标题为“地址没变,代码可能已经换过”,副标题“可升级代币合约:认出代理、找到升级权、翻出升级记录”,下方三格写着“认出代理和实现合约”“查升级权在谁手里”“数一数换过几次实现”
查可升级代币分三步:先确认它是代理、实现合约在哪,再顺着管理员或 owner 找到能升级的地址,最后看换过几次实现。

合约可升级,意思是你手里那个代币地址不变,但它背后跑的代码可以被人换掉。换一版代码,“不能增发、不能拉黑、没有卖出税”这几条都可能跟着变。

所以碰到可升级的币,问题不在“能不能升级”,而在三件事:它是不是代理、能按升级键的是谁、到现在换过几次。下面按这个顺序在区块浏览器上查,以 EVM 链(以太坊、BNB Chain 这类)为主,Solana 放在最后单说。

合约可升级到底换的是什么

可升级的代币一般拆成两个合约。你转账、授权时打交道的那个地址叫代理合约(proxy),它存着余额这些数据,自己几乎不写业务逻辑;真正决定“怎么转账、谁能增发”的代码放在另一个地址,叫实现合约(implementation,也有文档叫逻辑合约)。Etherscan 的代理合约说明写得很直白:代理把调用转交给实现合约执行,升级就是把实现地址换掉,代理地址不用变。

代理地址可以沿用,但不能据此保证余额和充提服务不受影响:新代码可能改动余额的读取与转账规则,交易所也可能暂停充提。ethereum.org 讲合约升级的文档把代价也写了:用户得信任开发者不会随意改动合约。

可升级不等于有问题。主流稳定币里就有代理结构,以太坊上的 USDC 在 Etherscan 合约页上标的就是代理。区别在于升级权握在谁手里、换之前给不给你反应时间。

怎么看出一个代币是代理合约

在 Etherscan 打开代币合约地址,进 Contract 标签,看这几处:

  • 有没有 Read as Proxy / Write as Proxy。出现了,说明浏览器已经认出它是代理,这两栏用的是实现合约的函数列表。以 USDC 为例,2026 年 11 月打开它的合约页,这一排依次是 Read as Proxy、Write as Proxy、Past Implementations、Implementation For。
  • Implementation 那一行。同一页上方会写 Implementation 加一个地址,USDC 那一行是 0x43506849…c054402dd,旁边还有一个代理类型的小标签(USDC 显示 ZeppelinOS)。点地址就跳到实现合约。
  • 只有一个 “Is this a proxy?” 按钮。说明浏览器在字节码里看到了 delegatecall,但还没确认。点它再点 Verify,识别成功后才会出现上面两栏。按 Etherscan 自己的说明,有这个按钮的也可能根本不是代理,比如只是调用了库合约。

Etherscan 在同一页还提醒了一句容易被忽略的话:它不会自动核实 Read as Proxy 里展示的代码,就是代理真正执行的代码。浏览器给的是推断,下面几步要拿链上的数去对。

认出代理以后,实现合约本身有没有开源要单独看,代理那层开源不代表实现那层也开源。这一项在代币合约没开源是什么意思里讲过,这里不重复。

浏览器没认出来时:EIP-1967 的三个存储位置

采用 EIP-1967 的代理,会把关键地址存在固定位置。这个标准存在的理由之一,就是让区块浏览器能直接读出代理信息给用户看。三个位置(存储槽)是:

  • 实现合约地址:0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc,改动时应发出 Upgraded 事件。
  • 管理员地址:0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103,标准里的说明是“允许升级这个代理的地址(可选)”,改动时应发出 AdminChanged 事件。
  • 信标合约地址:0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50,用信标结构时实现地址存在信标合约里,改动时应发出 BeaconUpgraded 事件。
EIP-1967 规范页的 Logic contract address 与 Beacon contract address 两节,分别列出存储槽的十六进制值、计算方式,以及 Upgraded 和 BeaconUpgraded 两个事件的定义
2026年11月 · EIP-1967 规范页:上半是实现地址的存储槽和 Upgraded 事件,下半是信标地址的存储槽,以及读取信标背后实现合约的两步。点击图片看大图。

存储槽里是 32 字节的值,地址在最后 20 字节。槽里全是 0,表示这一项没用上:比如用信标的代理,实现地址那一格按标准就该是空的。读这几个槽要用节点的 eth_getStorageAt 或者支持读存储的工具;只想知道答案的话,Etherscan 认出代理后显示的 Implementation 就是从这类位置读来的。

老一些的代理不一定按这个标准存,USDC 页面上标的 ZeppelinOS 就属于更早的一套写法。碰到这种,以浏览器识别结果和合约源码为准,别硬套上面三个槽。

升级权在谁手里:按代理结构找那个地址

能按升级键的人藏在哪,取决于代理用的是哪种结构。OpenZeppelin 的代理合约文档把常见的三种写得很清楚:

代理结构升级函数写在哪顺着找谁
透明代理(TransparentUpgradeableProxy)代理自带的升级入口,只接受管理员调用管理员槽里通常是一个 ProxyAdmin 合约,再读这个 ProxyAdmin 的 owner()
UUPS(UUPSUpgradeable)实现合约里的 upgradeToAndCall先读实现源码中 _authorizeUpgrade 的权限判断,再回代理地址的 Read as Proxy 读取 owner() 或角色;实现地址自身的读数不能代替代理读数
信标代理(BeaconProxy)信标合约(UpgradeableBeacon)信标合约的 owner();它改了 implementation() 返回的地址,跟随这个信标的代理才一起换实现

透明代理在 OpenZeppelin 5.x 里,部署时会自己建一个 ProxyAdmin,并把它写成不可更改的管理员;沿这个管理入口升级时,看的是 ProxyAdmin 的 owner,它可以通过转移所有权换人。在 Etherscan 上拿到 ProxyAdmin 地址后,再点进去读 owner。OpenZeppelin 也提醒:实现代码可能覆盖管理员槽,使槽里的地址与实际管理员不一致;碰到可疑实现,要结合部署记录和代理源码核对。

UUPS 的坑在另一头。升级逻辑写在实现合约里,文档要求开发者自己重写 _authorizeUpgrade 加上权限限制;合约另有防止意外升级到非 UUPS 实现的检查,它与谁能升级的权限检查是两件事。对持币人来说,这意味着每次升级之后,权限检查要对着新的实现合约重做一遍,不能拿旧版本的结论。

信标那一行最容易漏。你查的代理里实现槽是空的,信标槽里有地址,那就去信标合约看 implementation() 和 owner()。OpenZeppelin 5.x 的 BeaconProxy 把实际信标地址固定在部署时;信标槽若被实现代码覆盖,显示值可能与实际使用的信标不同,需对照部署参数和源码。

换过几次实现:Upgraded 事件和 Past Implementations

升级权在谁手里,回答的是“以后能不能换”。另一半是“以前换过没有”。两个地方能看:

  1. Etherscan 合约页的 Past Implementations。认出代理的合约,Contract 标签那一排里有这一项(USDC 的合约页上就有),用来查浏览器记录过的旧实现地址。
  2. 按代理结构找 Events:直接保存实现地址的代理看自己的 Upgraded;OpenZeppelin 的信标结构要看信标地址发出的 Upgraded,代理的 BeaconUpgraded 记录的是换信标地址。EIP-1967 建议初始化时也发事件,因此先排除首次设置实现的那条,再逐笔比较前后地址;仅重复写入同一地址,不算换了一个版本。

Etherscan 的说明里还有一条:实现地址升级过之后,要重新跑一次代理验证,新地址才会被它记下。因此页面上的 Implementation 可能落后于链上,事件也不能单独当完整历史:标准要求的是建议发出事件,自定义代码可能漏发。用日志找到交易后,再与当时的实现地址变更及当前链上读数对照;记录不全,就写清只能确认哪些变更。

看历史时盯这几种情况:上线没多久就连着换了几次实现;最近一次升级的新实现合约没有开源;管理员在大额解锁或上所前后刚换过人。单拿出来都不能定性,叠在一起,就该先停手把新实现的权限重查一遍。

读到升级权地址之后,分三种情况看

是一个普通钱包地址(EOA)。如果这个钱包能直接调用升级函数,且代码没有另外要求延迟或批准,一把私钥就能发起升级;链上不一定给你预留反应时间。

是一个多签合约。常规签名路径要达到设定门槛,还要查有没有可绕过这条路径的模块,以及签名人是否由同一方控制。Safe 多签的查法在代币权限交给 Safe 多签后怎么查签名人里一步步写了。

是一个时间锁合约。OpenZeppelin 的 TimelockController 有一个 getMinDelay() 读取函数,返回操作提交后至少要等多少秒才能执行。在时间锁的权限说明里,提案人、执行人和等待期是分开的;核对延迟是否大于零、是否有绕过时间锁的升级入口,才能判断有没有反应时间,卖出能否完成还取决于当时的转账状态与流动性。

还有一种:透明代理的 ProxyAdmin 已经放弃所有权(owner 读出来是零地址)。这会关闭经这个 ProxyAdmin 发起升级的路径,却不能单凭零地址断言整个代币永远不能升级:OpenZeppelin 文档明确提示,透明代理搭配 UUPS 实现时,可能还有实现合约里的升级入口。先查完所有可达的升级路径;增发、暂停等业务权限还在不在,要按在区块浏览器上查代币管理权限另查。

可升级的代币,之前查过的结论要怎么用

记录检查结果时,把区块高度和当前实现地址一起记下。带税代币的税率、能不能卖出、管理权限,既取决于实现代码,也取决于当前配置;税率、暂停状态或权限人可能不换实现就改变。发现实际实现变更后,要重新读代码和配置。

要在可升级和不可升级之间选,判断标准很简单:升级权落在一个普通钱包、又没有时间锁的币,相当于把规则交给一个人随时改,前面查到的每一条都只能当作“今天成立”;多签加有效时间锁能减少单个钱包直接改代码的机会,但不能保证你一定卖得出去。

记笔记时,代理地址、实现地址(信标结构再记信标地址)、升级权地址、核对时的区块高度,以及能确认的最近一次实现变更,都记下来。下次先对实现地址,再查权限和业务配置是否改变。

Solana 上的代币,问法换成程序的升级权限

Solana 没有 EVM 这套代理合约。普通 SPL 代币的转账规则由 Token Program 或 Token-2022 程序决定,发币方能控制的是 mint 上的铸币权限、冻结权限和各项扩展,查法见Token-2022 扩展列表怎么看。

“代码能不能被换”在 Solana 上落到程序(program)身上。Solana 程序文档写明:用可升级加载器(loader-v3)部署的程序,只要设置了升级权限就能升级,撤销这个权限后程序就不可再改。所以当一个币挂了转账钩子、或者流动性放在某个项目自建的程序里时,要去那个程序的页面看升级权限交给了谁、是不是已经撤销。

可升级是否危险,余额和锁定状态怎么判断

代币合约可升级,是不是说明项目方准备跑路?

不能这么下结论。以太坊上的 USDC 也是代理合约。可升级说明规则能被换,要看的是升级权在一个钱包、多签还是时间锁手里,以及历史上换过几次、新实现有没有开源。

代理合约升级以后,钱包里的余额会变吗?

余额数据保存在代理的存储里,单纯改实现地址不会自动清空存储。但升级时执行的代码可以改数据,存储布局不兼容也可能让余额被错误读取;不能保证升级前后余额一定相同。升级后要核对存储兼容性、余额逻辑和权限。

浏览器上看不到 Read as Proxy,是不是就不可升级?

不一定。浏览器可能还没识别,可以点 Is this a proxy 再验证一次;也可能是老式或自定义代理,存储位置不按 EIP-1967。不能只搜 delegatecall 或升级函数名:固定实现的代理也会用 delegatecall,自定义升级入口又可能换个名字。要结合代理和实现代码,确认是否还存在可达的实现替换路径。源码有没有公开,先按源码验证状态那篇查。