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);
// Since transfer throws an exception on failure and
// cannot call back here, there should be no way for us to
// still have half of the money.
assert(address(this).balance == balanceBeforeTransfer - msg.value / 2);
return address(this).balance;
}
}
对于上述合约,在哪种情况下断言失败/address(this).balance 不会减少 (msg.value / 2)?为什么我们需要在这里断言?
这个断言是正确的,这正是它存在的原因。你用assert()
宣告你认为永远有效的事情。如果它们被证明是错误的,那么你的合同中就有错误。
断言不仅仅是一种幻想if
。虽然它确实执行运行时检查,但它也是提供形式验证目标的方法之一。工具如SMT检查器 https://docs.soliditylang.org/en/latest/smtchecker.htmlSolidity 内置编译器可以通过尝试证明有关代码的各种陈述来检测错误。问题是 - 这样的工具如何判断你得到的结果不是你想要的结果?用断言记录您的假设是一种非常简单的方法,可以为工具提供区分预期行为和有缺陷行为所需的额外信息。
此外,虽然现在合约很简单并且很容易看出它不会失败,但代码不会永远保持简单。该条件仅在合同没有其他应付功能的假设下成立。每次添加付费功能时你会记得修改这个功能吗?如果合约增长并且该函数被埋在文件底部的几个其他函数下怎么办?最重要的是——其他人将来修改代码怎么办?他们会注意到这个限制吗?断言是一种好方法,不必依赖任何人注意到这一点并将其转变为自动检查。
最后,这个断言是正确的,但真的是这样吗?obvious?实际上有很多假设:
- Contract can receive ether only in a few specific ways:
- 通过调用其应付函数 -
sendHalf()
这里只有一个人
- 其召唤
receive()
or fallback()
功能 - 没有
- 是的接收者
selfdestruct
另一份合同的
- 成为区块中开采的以太币的接收者
- 的被叫方
transfer()
无法回电sendHalf()
因为transfer()
仅转发 2300 Gas,外部调用成本更高。
- 的被叫方
transfer()
不能执行selfdestruct
因为它要花费 5000 天然气。
- 恢复到内部
transfer()
不会以任何方式被压制,所以即使付出的代价selfdestruct
将 future 更改为
- 以太坊上的交易只能按顺序执行,开采的以太币不能在合约执行过程中转移。
这里有足够的假设,代码的作者可能根本无法 100% 确定他没有错过一些可能会变成安全漏洞的晦涩的极端情况。断言可以是明确排除这种可能性的简单而有效的方法。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)