Solidity Learning: Revert(), Assert(), and Require() in Solidity, and the New REVERT Opcode in the EVM

即將到來的變化以及它們如何運作

Crosspost:這篇文章最初由ConsenSys的“Maurelian”撰寫,可以在這裡找到。

Solidity版本0.4.10的發布引入了 assert(), require()revert()函數,並且自此以後就出現了混亂。

具體來說, assert()require()“guard”函數提高了合同代碼的可讀性,但區分它們可能相當混亂。

在這篇文章中,我會:

  1. 解釋這些功能解決的問題。
  2. 討論Solidity編譯器如何處理新的 assert(), require()revert().
  3. 給出一些經驗法則來決定如何以及何時使用每一個。

為了方便起見,我已經使用這些功能創建了一個簡單的合約,您可以在混音( remix.)中進行測試。

如果你真的想要一個TLDR,這個答案在ethereum stackexchange應該做到。

說你的合同有一些特殊的功能,只能由被指定為 owner的特定地址來調用。

在Solidity 0.4.10之前(以及之後的一段時間),這是執行權限的常見模式:

contract HasAnOwner {
address owner;

function useSuperPowers(){
if (msg.sender != owner) { throw; }
// do something only the owner should be allowed to do
}
}

如果 useSuperPowers()函數被owner以外的任何人調用,函數將拋出返回一個無效的操作碼 (invalid opcode)錯誤,撤消所有狀態改變,並用盡所有剩餘的氣體(參見本文以獲取更多關於天然氣和費用的信息)。

現在拋棄了throw關鍵字,最終將被完全刪除。幸運的是,新函數assert(), require() 和revert()提供了相同的功能,語法更清晰。

讓我們看看如何更新,if .. throw 模式與我們的新守衛功能。

這一行:

if(msg.sender != owner) { throw; }

目前的行為與以下所有行為完全相同:

  • if(msg.sender != owner) { revert(); }
  • assert(msg.sender == owner);
  • require(msg.sender == owner);

請注意,在 assert() require()示例中,條件語句是 if 塊條件的反轉,將比較運算符 !=切換為 ==

區分 assert() 和require()

首先,為了幫助分離這些“警衛”功能,可以將 assert()想像為一個過分自信的欺負者,他們竊取你所有的gas。然後設想 require()作為一種禮貌的管理類型,他會調用你的錯誤,但更寬容

用這種助記方法,這兩個函數之間的真正區別是什麼?

在Byzantium網絡升級之前, require()assert()實際上表現相同,但它們的字節碼輸出稍有不同。

  1. assert()使用 0xfe操作碼來導致錯誤情況
  2. require()使用 0xfd操作碼來導致錯誤情況

如果你在黃色白皮書( yellow paper)上查找這些操作碼中的任何一個,你都不會找到它們。這就是為什麼你會看到無效的 invalid opcode,因為沒有關於客戶端如何處理它們的規範。

然而,在Byzantium之後,以及在Ethereum虛擬機中實施EIP-140:REVERT指令後,情況將會發生變化。然後, 0xfd操作碼將被映射到 REVERT指令。

這是我發現真正迷人的地方:

從版本0.4.10開始,已經部署了許多合同,其中包括一個新的操作碼,該操作碼處於休眠狀態,直到它不再有效。屆時,它會醒來,變成 REVERT!

注意: throwrevert()也使用 0xfd。在0.4.10之前。 throw 使用0xfe

REVERT操作碼將做什麼

REVERT仍然會撤消所有的狀態變化,但它將以兩種方式處理與“無效操作碼”不同的處理:

  1. 它將允許你返回一個值。
  2. 它會將剩餘的氣體退還給主叫方。

大多數智能合約開發人員都非常熟悉臭名昭著的無用操作碼錯誤。幸運的是,我們很快就能夠返回錯誤消息或與錯誤類型相對應的數字。

這看起來像這樣:

revert(‘Something bad happened’);

require(condition, ‘Something bad happened’);

注意:solidity不支持此返回值參數,但您可以為該更新觀察此問題

選擇 revert(), assert() and require()

因此,如果revert()require() ()都會退還所有剩餘的gas,並允許您返回一個值,, 為什麼要使用 assert()來燃燒氣體(gas)?

不同之處在於字節碼(bytecode)輸出,為此我將引用文檔(重點介紹):

The require function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts. If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing assert.Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix.

了澄清這一點:它應該被認為是 require() 語句失敗的正常和健康事件(與revert()相同). 當 assert() 語句失敗時,發生了一些非常錯誤和意外的事情,您需要修復代碼。

通過遵循這一指導原則,靜態分析形式驗證工具將能夠檢查您的合同,以發現並證明可能違反合同的條件,或者證明您的合同按設計運行時沒有缺陷。

在實踐中,我使用了一些啟發式方法來幫助我確定哪些是合適的。

使用require()來:

  • 驗證用戶輸入即。要求require(input<20);
  • 驗證來自外部合同的響應,即。要求 require(external.send(amount));
  • 在執行之前驗證狀態條件,即。要求 require(block.number > SOME_BLOCK_NUMBER) 或要求require(balance[msg.sender]>=amount)
  • 一般來說,你應該最經常使用 require
  • 通常,它將用於函數的開始

在我們的智能合同最佳實踐中,有很多 require() 用於這類事情。

使用revert()來:

  • 處理與 require()相同類型的情況,但具有更複雜的邏輯。

如果你有一些複雜的嵌套的 if/else 邏輯流程,你可能會發現使用 revert()而不是 require()是有意義的。請記住,複雜的邏輯是代碼味道

使用assert() 來:

  • 檢查 overflow/underflow,例如. c = a+b; assert(c > b)
  • 檢查不變量(invariants), 例如. assert(this.balance >= totalSupply);
  • 進行更改後驗證狀態
  • 防止永遠不可能發生的情況
  • 一般來說,你可能會使用更少的 assert
  • 一般來說,它將用於函數的結尾

基本上, require() 應該是你去檢查條件的函數,assert() 只是為了防止發生任何真正的壞事情,但它不應該是可能的條件評估為 false.

另外: “你不應該盲目地使用 assert 來進行溢出檢查,但是只有當你認為之前的檢查(使用 ifrequire之一) 會導致溢出不可能時”. — comment from @chriseth

這些功能對於您的安全工具箱來說是非常強大的工具。了解如何使用它們以及何時使用它們不僅有助於防止漏洞,還可以使您的代碼更加便於用戶使用,並且可以針對即將發生的變化提供未來證明。

Written by

撰寫任何事情,O型水瓶混魔羯,咖啡愛好者,Full stack/blockchain developer,Founder of Blockchain&Dapps meetup and DeFi-Decentralized-Finance-SG meetup,Udemy teacher。

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store