Sharer智能合约中assert的使用原因及失败触发条件是什么?
contract Sharer { function sendHalf(address payable addr) public payable returns (uint balance) { require(msg.value % 2 == 0, "Even value required."); uint balanceBeforeTransfer = address(this).balance; addr.transfer(msg.value / 2); // 由于transfer失败时会抛出异常,且不会回调本合约,理论上不可能仍剩余一半转账金额 assert(address(this).balance == balanceBeforeTransfer - msg.value / 2); return address(this).balance; } }
1. assert断言失败的场景
- 最常见的可主动触发的场景是传入的
addr为合约地址,且该合约实现了payable类型的fallback()或receive()回调函数,回调逻辑中会向当前Sharer合约转入ETH,且整个回调操作的gas消耗不超过transfer()方法固定提供的2300 gas上限。- 最典型的攻击实现就是接收方合约在回调中执行
selfdestruct(payable(address(this))),强制将自身所有余额转入Sharer合约,该操作gas消耗极低,完全可以在2300 gas限制内完成。此时transfer()执行完成后,Sharer合约除了扣减转出去的msg.value / 2,还会收到接收方合约转回的ETH,最终余额就会不等于balanceBeforeTransfer - msg.value / 2,触发assert失败。
- 最典型的攻击实现就是接收方合约在回调中执行
- 还有极特殊的被动链上场景也会触发:比如在当前
sendHalf交易执行过程中,刚好有其他交易通过selfdestruct强制向Sharer合约转入ETH、或者矿工将该区块的coinbase奖励设置为Sharer合约地址,且这部分额外的ETH入账发生在balanceBeforeTransfer赋值完成后、assert校验执行前,也会导致余额不符合预期。
2. 此处assert断言的作用
- Solidity中
assert()专门用于校验开发者认定的永远不应该被打破的内部逻辑不变量,一旦触发就代表出现了预期外的严重逻辑漏洞/攻击行为,触发时会消耗所有剩余gas并回滚整个交易的所有状态变更。 - 本合约中开发者原本的假设是:
transfer()要么执行失败直接抛异常回滚,要么执行成功且不会触发影响本合约余额的回调,因此转账完成后余额的减少量一定等于msg.value / 2。此处加assert就是为了防御所有违反这个假设的异常场景,一旦出现就立刻回滚,避免后续逻辑基于错误的余额执行产生更大的资产损失。
内容的提问来源于stack exchange,提问作者Cengo
相关产品推荐
相关产品推荐

