send()、transfer()是否隐式减少合约余额?L3扣减逻辑必要性问询
原生转账方法的余额变动规则
Solidity里的send()、transfer()是针对EVM原生资产(如ETH、BNB)的底层转账接口,调用时会自动隐式扣减发起方的原生资产余额:
- 原生资产的余额是EVM节点维护的全局状态,不是合约内部定义的存储变量,不需要开发者手动写代码修改余额数值,转账执行成功后链上状态会自动更新。
- 两个接口的核心差异:
transfer()转账时固定给目标地址分配2300gas,转账失败会直接回滚整个交易;send()同样只传递2300gas,但失败时仅返回false,不会自动回滚,必须手动判断返回值做异常处理,否则容易出现转账失败但后续逻辑继续执行的逻辑漏洞。
ERC20转账逻辑的必填项与安全风险
给出的ERC20标准转账核心状态更新代码如下:
L1: require(sender != address(0), "ERC20: transfer from the zero address"); L2: require(recipient != address(0), "ERC20: transfer to the zero address"); L3: _balances[sender] = _balances[sender].sub(amount); L4: _balances[recipient] = _balances[recipient].add(amount);
针对L3行的问题结论非常明确:
- L3行是不可缺失的必填核心逻辑。和原生资产由EVM全局维护余额不同,ERC20本身是合约层面发行的代币,所有地址的代币余额都存在合约自己的存储映射
_balances里,所有余额变动必须由合约代码显式更新,EVM不会自动修改这个映射的数值。 - 如果删掉L3行的发送方余额扣减逻辑,会直接引发比双花更严重的无限超发漏洞:
- 发送方地址的余额记录永远不会被扣除,哪怕它已经把等额代币转给了其他地址,它的账面余额还是保持原有数值,可以无限次重复调用转账接口,把同一笔“空气”转给无数个接收地址
- 每次转账只会给接收方增加余额,合约统计的总供应量会无上限膨胀,完全失去代币的价值锚定,属于致命级别的合约漏洞。
- 额外安全提示:标准实现中要遵循「检查-生效-交互」(CEI)安全模式,必须先做余额扣减,再做接收方余额增加,最后再触发转账事件、执行外部调用,同时要配合整数溢出校验(Solidity 0.8版本内置,低版本需要引入SafeMath库),避免重入、整数溢出类漏洞。
内容的提问来源于stack exchange,提问作者Divya
相关产品推荐
相关产品推荐

