本篇文章从技术角度展示了针对 Solidity 的异常情况如何在代码层面进行详细分析。…以太坊,技术,安全...
FxAjax ChainNews:
Solidity 是以太坊上最主流的函数修改器。本篇文章从技术角度展示了如何针对异常情况在代码层面进行详细分析,最后总结出异常情况最易出现的一些场景,并给出相对应的建议。
来源 | 矩阵数字经济智库 作者 | 闫莺等
函数修改器Solidity 提供了一个函数修改器(Function Modifiers)的特性。 函数修改器与 Python 中的装饰器类似,可以在一定程度上改变函数的行为,比如可以自动在函数执行前检查参数是否合法。函数修改器是可以被继承的,同时可以被派生类覆盖重写。
下面代码展示了如何声明并使用函数修改器。
contract owned {function owned() { owner = msg.sender; }address owner;// 这个合约定义了一个在派生合约中使用的函数修改器// ";" 指代被修改函数的函数体。// 当这个函数执行前,先检查 msg.sender 是否是合约创建者// 如果不是就会抛出异常modifier onlyOwner {require(msg.sender == owner);;}}contract Contract is owned {// 从 owned 合约继承了 onlyOwner 函数修改器并且将其作用于 close 函数// 确保了这个函数只有在调用者为合约创建者时才会生效function close() onlyOwner { selfdestruct(owner); }}下面代码进一步展示了函数修改器是如何接收参数的,函数修改器的参数可以是上下文中存在的任意变量组成的表达式。
contract priced {// 函数修改器可以接收参数modifier costs(uint price) {if (msg.value >= price) {_; }}}contract Register is priced, owned {mapping (address => bool) registeredAddresses;uint price;function Register(uint initialPrice) { price = initialPrice; }// 这里需要 payable 修饰词,否则无法通过该方法转账以太币// 函数修饰器 costs 接收参数 price// 使用 costs 修饰器确保 registe 函数在 msg.val 大于 price 时才会生效function registe() payable costs(price) {registeredAddresses[msg.sender] = true;}function changePrice(uint_price) onlyOwner {price =price;}}contract Mutex {bool locked;modifier noReentrancy() {require(!locked); locked = true;; locked = false;}// 这个函数使用了 noReentrancy 修改器,这保证了在 f 函数内部无法再次调用 f 函数// 在执行 return 7 时也执行了函数修改器中的 locked = false 语句function f() noReentrancy returns (uint) {require(msg.sender.call()); return 7;}}异常处理以太坊使用状态回退机制处理异常。如果发生了异常,当前消息调用和子消息调用产生的所有状态变化都将被撤销并且返回调用者一个报错信号。Solidity 语言提供了两个方便的函数 assert 和 require 来检查条件,并且当条件不满足的时候抛出一个异常。assert 函数通常用于检查变量和内部错误,require 函数用于确保程序执行的必要条件是成立的。一个正常运行的程序不应该遇到 assert 和 require 失败,否则程序代码中一定存在需要修复的问题。revert 函数和 throw 关键词会标识发生了错误并且回退当前的消息调用产生的状态改变。当前调用收到子消息调用产生的异常时会自动抛出,所以异常会一层层上浮直到最上层的根调用,代码会立刻终止执行并回退状态改变。但是, .send、call、delegatecall 是例外,这些函数在执行过程中抛出异常会返回 false,而不是自动抛出异常。下面的例子展示了如何使用 assert 和 require 确保程序正确运行。contract AssertExample {function sendHalf(address addr) payable returns (uint balance) {require(msg.value % 2 == 0); // 只允许偶数 uint balanceBeforeTransfer = this.balance; addr.transfer(msg.value / 2); // 使用 assert 确保 transfer 转账成功,否则抛出异常 assert(this.balance == balanceBeforeTransfer - msg.value / 2); return this.balance;}}一个 assert 类型的异常会在下述场景抛出:
访问数组越界,下标为负数或者超出长度。访问固定长度的 bytesN 越界,下标为负数或者超出长度。对 0 做除法或者对 0 取模,比如 5/0,5%0。移位操作使用了一个负数做操作数。转换一个特别大的数或者负数到枚举类型变量。使用移位操作时给一个负数值。使用枚举时将过大值或赋值转为枚举类型变量。调用 assert 函数并且参数值为 false。一个 require 类型的异常会在下述场景抛出:
调用 throw。调用 require 并且参数值为 false。发起一个消息调用,但是这个调用没有正常完成,比如 Gas 耗尽、被调用函数不存在或者函数本身抛出一个异常。(.send、call、delegatecall 例外)使用 new 创建一个合约,但是和 3 中提到的原因一样构造函数没有正常完成。调用外部函数时指向一个不包含代码的地址。合约通过一个没有 payable 修辞词的函数接收以太币,包括构造函数和 fallback 函数。合约通过一个公开的访问函数接收以太币。.transfer() 失败。在 require 类型的异常发生时会执行回退操作(指令号 0xfd),对于一个 assert 类型的异常执行一个无效操作(指令号 0xfe)。在这两种情况下,以太坊虚拟机都会撤销所有的状态改变。这样做是因为发生了意料之外的情况,交易无法安全执行下去,为了保证交易的原子性,最安全的操作就是撤销该交易对状态造成的影响。
在编写合约代码时,我们需要合理使用 assert 和 require 来保证代码能够按我们预期的设计进行。
更多精彩内容,关注FxAjax ChainNews 公众号(id:chainnewscom),或者来微博 **@ FxAjax ChainNews**与我们互动!转载请注明版权和原文链接!
相关文章
21Shares携手法国兴业银行,强化德国及东欧市场加密ETP流动性
CME集团6月30日推出现货报价期货:比特币、以太坊及标普500等长达5年持有
eToro推出加密资产转账与兑现功能:比特币、以太坊可无缝投资多资产
nxtAssets GmbH携手SIX推出比特币与以太坊ETP:加密资产交易总数达398
Clearstream携手Crypto Finance推出加密资产托管服务,德意志交易所集团引领机构投资者新风潮
西班牙BBVA获批推出比特币和以太坊交易服务,零售客户可通过手机银行直接管理加密资产
Bybit遭遇重大黑客攻击,15亿美元数字货币被盗
Bybit遭遇安全漏洞,1.4亿美元以太坊代币被盗,市场反应激烈
Bybit交易所遭黑客攻击,14亿美元ETH被盗,资金安全隐患引发行业关注
以太坊 ETF 在美国上市首日交易量达 10 亿美元,占比特币交易量的 23%
现货以太 ETF 将于今日在 SEC 最终批准后开始交易
芝商所推出以微欧元计价的比特币和以太坊期货
Visa (NYSE:V) 設想在以太坊網絡上進行自動支付
MetaMask“Portfolio Dapp”新增跨链桥服务
0xScope:Alameda以太坊上627个地址总资产约1亿美元
Paradigm:BTC和ETH看跌期权的需求重新出现
Chainlink与MVGX合作探索加密碳交易
币安明日将进行约两个小时的以太坊钱包维护,届时将暂停ERC-20存取款
The Block研究副总裁:Alameda链上余额较10月1日减少47%,大部分资金存入FTX和Genesis
Circle将于2023年上半年将Euro Coin引入Solana



