ARTICLE · 1330405
USDT 智能合约源码拆解:mint、转账与权限控制
USDT 智能合约源码拆解:mint、转账与权限控制
把以太坊上那份"祖传"合约摊开看,老代码里全是教科书级的设计取舍
系列三十二「稳定币技术拆解」第2篇
💡 你将学到:一套真实稳定币的完整权限矩阵、铸币与赎回的代码路径、为什么老合约至今还稳如老狗
先上一段代码热热身。下面是以太坊上 USDT 合约(节选自公开验证源码)的骨架,我做了注释翻译——先别细抠语法,感受一下它的"年代感":
tether_skeleton.sol
pragma solidity ^0.4.17;// 注意:0.4 时代的代码,比现在主流低了四个大版本
contract TetherToken {
stringpublic name;
stringpublic symbol;
uint8public decimals = 6;// 注意:6 位小数,不是 18
uint256public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
addresspublic owner;// 发行方控制地址
addresspublic upgradeAgent;// 历史遗留:早期版本升级接口
uintpublic deprecateDelegate;
mapping(address => bool) public blackList;// 冻结名单
boolpublic paused;// 全局暂停开关
...
}
三处细节值得停一停:0.4 版本的编译器(现在的新合约基本都是 0.8+)、6 位小数(对齐美元的两位小数再加冗余)、以及两个敏感词级别的变量——blackList 和 paused。后文一一展开。
01 一、权限矩阵:谁能动哪把刀
老派合约的权限控制没有 OpenZeppelin 那套花哨的 Role 系统,就是简单的 owner 判断。把所有受限函数列出来,权限结构一目了然:
•**铸币(issue)**:仅 owner。发行方在收到法币入金凭证后增发,是全网唯一的新增入口
•**赎回(redeem)**:仅 owner。销毁流通量,对应出金
•**冻结(blackList)**:仅 owner。将地址加入/移出冻结名单
•**暂停(pause)**:仅 owner。全局停止转账,应急开关
•**设置升级代理(setUpgradeAgent / deprecate)**:仅 owner。历史遗留能力
六把刀全部握在一个地址手里。这种设计放今天会被审计骂出天际(单点风险),但在"发行方 = 中心化信用主体"的稳定币语境下,它反而是符合业务模型的——因为稳定币的本质承诺就是"有一个主体对总量负责"。权限集中和中心化信用,在这里是自洽的。
02 二、转账函数:19 行代码里的四个拦截器
看核心的 transfer 实现(简化还原):
transfer.sol
function transfer(address _to, uint _value) public onlyPayloadSize(2) {
// 拦截器 1:全局暂停
if (paused) throw;
// 拦截器 2:发送方是否被冻结
if (blackList[msg.sender]) throw;
// 拦截器 3:接收方是否被冻结
if (blackList[_to]) throw;
// 经典三查:余额、溢出、非零
if (balanceOf[msg.sender] < _value) throw;
if (balanceOf[msg.sender] + _value < balanceOf[msg.sender]) throw;
if (balanceOf[_to] + _value < balanceOf[_to]) throw;
balanceOf[msg.sender]-= _value;
balanceOf[_to]+= _value;
Transfer(msg.sender,_to, _value);// 老版本的事件写法
}
逐个看这些拦截器。onlyPayloadSize 是个有趣的古董——0.4 时代没有 0.8 的自动溢出检查,也没有 EIP-20 的 Short Address Attack 防护标准,作者手写了这个修饰器来防"调用数据长度被恶意截断"的攻击。现代编译器已经让这类手写防护进了博物馆,但读懂它,你就理解了智能合约安全史的一段演进。
paused 和 blackList 两个拦截器合起来,意味着每一笔转账前都要多查两档状态。对用户来说,这就是"稳定币转账可能被拒"的技术根源——它不是 bug,是写在代码里的合规动作。
03 三、铸币与赎回:总量变化的唯一通道
issue.sol
function issue(uint amount) public onlyOwner {
require(balances[owner] + amount > balances[owner]);
require(totalSupply + amount > totalSupply);// 老式溢出检查
balances[owner]+= amount;// 先记到发行方名下
totalSupply+= amount;
Issue(amount);// 增发事件,链上全程可审计
}
function redeem(uint amount) public onlyOwner {
require(totalSupply >= amount);
require(balances[owner] >= amount);
balances[owner]-= amount;
totalSupply-= amount;
Redeem(amount);// 销毁事件
}
两条路径都只认 owner,都强制发事件。这意味着一个重要事实:这个合约的全部总量变化,链上可查、有账可对。上一篇文章讲的"各链铸销对账",审计上就是靠这些 Issue/Redeem 事件串起来的。
04 四、给老代码挑刺:三个今天不会这么写的点
读完源码,反过来看看哪些写法已经过时——这也是把老合约当教材的价值所在:
•**没有自定义错误**:所有失败都 `throw`,今天会换成 `error Unauthorized()` 这类自定义错误,Gas 省一大截
•**手写溢出检查**:0.8+ 编译器内置溢出回滚,那些 `+ amount > totalSupply` 的判断今天可以全删
•**单 owner 无时限**:今天的稳定币普遍会给关键权限加时间锁(Timelock),敏感操作要等一段时间才生效,给市场留反应窗口
老代码不是烂代码,它是特定时期的合理答案。读它的意义,是让你知道"现在为什么这样写"。
⚠️ 本文源码为教学节选与简化,以链上公开验证的完整源码为准。仅作技术探讨,不构成任何操作建议。
✅ 六把权限刀集中在一个 owner 地址,与中心化信用主体自洽
✅ 转账函数四个拦截器:全局暂停、双方冻结检查、老式溢出防护
✅ issue/redeem 是总量变化的唯一通道,事件全程可审计
✅ 老代码不是烂代码:手写溢出检查都是那个年代的合理答案
─────────────────────────
📖 下篇预告:稳定币脱锚事件复盘:锚定是怎么被打破又修复的
📢 本文由「区块链编程」创作出品
未经授权,禁止转载
如有转载需求,请联系作者
👉 关注「区块链编程」
关注我,解锁更多可能














































































