solidity--語言基礎(chǔ)

# solidity源文件結(jié)構(gòu) - `// SPDX-License-Identifier: MIT` - `pragma solidity ^0.5.2;` - `pragma abicoder v1;` - `import "filename";` - 注釋 # 智能合約組成 ### 狀態(tài)變量 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.0 <0.9.0; contract SimpleStorage { uint storedData; // State variable // ... } ``` ### 函數(shù)/方法 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.1 <0.9.0; contract SimpleAuction { function bid() public payable { // Function // ... } } // Helper function defined outside of a contract function helper(uint x) pure returns (uint) { return x * 2; } ``` ### 修飾器 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.22 <0.9.0; contract Purchase { address public seller; modifier onlySeller() { // Modifier require( msg.sender == seller, "Only seller can call this." ); _; } function abort() public view onlySeller { // Modifier usage // ... } } ``` ### 事件 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.21 <0.9.0; contract SimpleAuction { event HighestBidIncreased(address bidder, uint amount); // Event function bid() public payable { // ... emit HighestBidIncreased(msg.sender, msg.value); // Triggering event } } ``` ### 異常 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.4; /// Not enough funds for transfer. Requested `requested`, /// but only `available` available. error NotEnoughFunds(uint requested, uint available); contract Token { mapping(address => uint) balances; function transfer(address to, uint amount) public { uint balance = balances[msg.sender]; if (balance < amount) revert NotEnoughFunds(amount, balance); balances[msg.sender] -= amount; balances[to] += amount; // ... } } ``` ### 結(jié)構(gòu)類型 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.0 <0.9.0; contract Ballot { struct Voter { // Struct uint weight; bool voted; address delegate; uint vote; } } ``` ### 枚舉類型 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.0 <0.9.0; contract Purchase { enum State { Created, Locked, Inactive } // Enum } ``` # 變量類型 Solidity 是一種靜態(tài)類型語言缤谎,這意味著需要指定每個變量(狀態(tài)和局部)的類型托呕。Solidity 提供了幾種基本類型项郊,它們可以組合起來形成復(fù)雜類型差油。 此外厌殉,類型可以在包含運算符的表達(dá)式中相互交互。有關(guān)各種運算符的快速參考楼眷,請參閱[運算符的優(yōu)先順序](https://docs.soliditylang.org/en/latest/types.html#order)。 “undefined”或“null值的概念在 Solidity 中不存在张吉,但新聲明的變量始終具有取決于其類型的[默認(rèn)值。](https://docs.soliditylang.org/en/latest/control-structures.html#default-value)要處理任何意外值伦忠,您應(yīng)該使用[revert 函數(shù)](https://docs.soliditylang.org/en/latest/control-structures.html#assert-and-require)來回滾整個事務(wù),或者返回一個元組赋咽,其中第二個`bool`值表示成功。 ## 值類型 - `bool`: true亦镶、false - `int/uint`:uint 和 int 分別是 uint256 和 int256 的別名。 - 具有加绊起、減、乘笋鄙、除、模怪瓶、指數(shù)計算 - address: - `address`:一個 20 字節(jié)的值(以太坊地址的大姓裔)许布。 - `address payable`:與address一樣蜜唾,但是具有`transfer`和`send`功能 - 這種區(qū)別背后的想法是,`address payable`是一個你可以將以太幣發(fā)送到的地址泌霍,而你不應(yīng)該將以太幣發(fā)送到一個普通地址,例如藤为,因為它可能是一個不是為接受以太幣而構(gòu)建的智能合約分别。 - 類型轉(zhuǎn)換: - 允許從 `address payable` 到 `address` 的隱式轉(zhuǎn)換耘斩,而從 `address` 到 `address payable` 的轉(zhuǎn)換必須通過 `payable( )` 顯式轉(zhuǎn)換。 - 對于 `uint160`桅咆、整型文字括授、`bytes20` 和合約類型,允許與地址進(jìn)行顯式轉(zhuǎn)換岩饼。 - 只有`address` 和合約類型的表達(dá)式才能通過顯式轉(zhuǎn)換`payable(...)` 轉(zhuǎn)換成`address payable` 類型荚虚。 對于合約類型籍茧,只有當(dāng)合約可以接收以太幣時才允許這種轉(zhuǎn)換版述,即合約具有接收或支付回退功能。 請注意寞冯,`payable(0)` 是有效的并且是此規(guī)則的例外渴析。 ## address類型支持的方法 - `balance`?:查詢地址的余額 - `transfer`:發(fā)送eth到可支付的地址(如果當(dāng)前合約沒有足夠多的余額或eth交易被接受地址拒絕,該方法會失敗回滾) ```solidity address payable x = payable(0x123); address myAddress = address(this); if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); ``` 如果 x 是合約地址简十,它的代碼(更具體地說:它的 Receive Ether Function檬某,如果存在,或者它的 Fallback Function螟蝙,如果存在)將與轉(zhuǎn)移調(diào)用一起執(zhí)行(這是 EVM 的一個特性恢恼,無法阻止 ). 如果執(zhí)行耗盡 gas 或以任何方式失敗,以太幣轉(zhuǎn)移將被還原胰默,當(dāng)前合約將異常停止场斑。 - `send`:`send`是`transfer`的低層對應(yīng)。如果執(zhí)行失敗牵署,當(dāng)前合約不會異常停止漏隐,但是`send`會返回`false` - `call`,?`delegatecall`?和?`staticcall`:為了與不遵守 ABI 的合約進(jìn)行交互,或者為了更直接地控制編碼奴迅,提供了函數(shù) `call`青责、`delegatecall` 和 `staticcall`。 它們都采用單個字節(jié)內(nèi)存參數(shù)并返回成功條件(作為布爾值)和返回的數(shù)據(jù)(字節(jié)內(nèi)存)取具。 函數(shù) `abi.encode`脖隶、`abi.encodePacked`、`abi.encodeWithSelector` 和 `abi.encodeWithSignature` 可用于對結(jié)構(gòu)化數(shù)據(jù)進(jìn)行編碼暇检。 - 只有`call`可以發(fā)送eth:`address(nameReg).call{value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));` ```solidity bytes memory payload = abi.encodeWithSignature("register(string)", "MyName"); (bool success, bytes memory returnData) = address(nameReg).call(payload); require(success); ``` ## 合約類型 每個合約都定義了自己的類型产阱。 您可以隱式地將合同轉(zhuǎn)換為它們繼承自的合同。 合同可以顯式轉(zhuǎn)換為地址類型或從地址類型轉(zhuǎn)換块仆。 僅當(dāng)合同類型具有接收或應(yīng)付回退功能時构蹬,才可能與地址應(yīng)付類型進(jìn)行顯式轉(zhuǎn)換王暗。 轉(zhuǎn)換仍然使用 address(x) 執(zhí)行。 如果合約類型沒有 receive 或 payable 回退函數(shù)庄敛,則可以使用 payable(address(x)) 轉(zhuǎn)換為 address payable俗壹。 您可以在有關(guān)地址類型的部分中找到更多信息。 如果您聲明一個合同類型的局部變量 (MyContract c)铐姚,您可以調(diào)用該合同的函數(shù)策肝。 注意從具有相同合同類型的地方分配它肛捍。 您還可以實例化合約(這意味著它們是新創(chuàng)建的)隐绵。 合約的數(shù)據(jù)表示與地址類型相同,這種類型也用在 ABI 中拙毫。 合同不支持任何運營商依许。 合約類型的成員是合約的外部函數(shù),包括任何標(biāo)記為公共的狀態(tài)變量缀蹄。 對于合約 C峭跳,您可以使用 type(C) 來訪問有關(guān)合約的類型信息。 ## 固定大小的字節(jié)數(shù)組 值類型 `bytes1`缺前、`bytes2`蛀醉、`bytes3`、…衅码、`bytes32` 包含從 1 到 32 的字節(jié)序列拯刁。 ## 枚舉 枚舉是在 Solidity 中創(chuàng)建用戶定義類型的一種方式。 它們可以顯式轉(zhuǎn)換為所有整數(shù)類型逝段,但不允許隱式轉(zhuǎn)換垛玻。 從整數(shù)的顯式轉(zhuǎn)換會在運行時檢查值是否位于枚舉范圍內(nèi),否則會導(dǎo)致 Panic 錯誤奶躯。 枚舉至少需要一個成員帚桩,聲明時的默認(rèn)值是第一個成員。 枚舉不能超過 256 個成員嘹黔。 數(shù)據(jù)表示與 C 中的枚舉相同:選項由從 0 開始的后續(xù)無符號整數(shù)值表示账嚎。 使用 type(NameOfEnum).min 和 type(NameOfEnum).max 您可以獲得給定枚舉的最小值和最大值。 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.8; contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } ActionChoices choice; ActionChoices constant defaultChoice = ActionChoices.GoStraight; function setGoStraight() public { choice = ActionChoices.GoStraight; } // Since enum types are not part of the ABI, the signature of "getChoice" // will automatically be changed to "getChoice() returns (uint8)" // for all matters external to Solidity. function getChoice() public view returns (ActionChoices) { return choice; } function getDefaultChoice() public pure returns (uint) { return uint(defaultChoice); } function getLargestValue() public pure returns (ActionChoices) { return type(ActionChoices).max; } function getSmallestValue() public pure returns (ActionChoices) { return type(ActionChoices).min; } } ``` ## 用戶定義的值類型 用戶定義的值類型允許在基本值類型上創(chuàng)建零成本抽象儡蔓。 這類似于別名郭蕉,但具有更嚴(yán)格的類型要求。 用戶定義的值類型使用`type?C?is?V` 定義浙值,其中 C 是新引入類型的名稱恳不,V 必須是內(nèi)置值類型(“底層類型”)。 函數(shù) `C.wrap` 用于將基礎(chǔ)類型轉(zhuǎn)換為自定義類型开呐。 同樣烟勋,函數(shù) `C.unwrap` 用于將自定義類型轉(zhuǎn)換為基礎(chǔ)類型规求。 C 類型沒有任何運算符或附加的成員函數(shù)。 特別是卵惦,甚至連運算符 == 都沒有定義阻肿。 不允許與其他類型進(jìn)行顯式和隱式轉(zhuǎn)換。 這種類型的值的數(shù)據(jù)表示是從底層類型繼承的沮尿,底層類型也在 ABI 中使用丛塌。 以下示例說明了自定義類型 `UFixed256x18`,它表示具有 18 位小數(shù)的十進(jìn)制定點類型和用于對該類型執(zhí)行算術(shù)運算的最小庫畜疾。 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.8; // Represent a 18 decimal, 256 bit wide fixed point type using a user-defined value type. type UFixed256x18 is uint256; /// A minimal library to do fixed point operations on UFixed256x18. library FixedMath { uint constant multiplier = 10**18; /// Adds two UFixed256x18 numbers. Reverts on overflow, relying on checked /// arithmetic on uint256. function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) { return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b)); } /// Multiplies UFixed256x18 and uint256. Reverts on overflow, relying on checked /// arithmetic on uint256. function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) { return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b); } /// Take the floor of a UFixed256x18 number. /// @return the largest integer that does not exceed `a`. function floor(UFixed256x18 a) internal pure returns (uint256) { return UFixed256x18.unwrap(a) / multiplier; } /// Turns a uint256 into a UFixed256x18 of the same value. /// Reverts if the integer is too large. function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) { return UFixed256x18.wrap(a * multiplier); } } ``` 請注意 `UFixed256x18.wrap` 和 `FixedMath.toUFixed256x18` 如何具有相同的簽名但執(zhí)行兩個截然不同的操作:`UFixed256x18.wrap` 函數(shù)返回一個 `UFixed256x18`赴邻,它與輸入具有相同的數(shù)據(jù)表示,而 `toUFixed256x18` 返回一個具有相同數(shù)值的 `UFixed256x18`啡捶。 ## 方法類型 函數(shù)類型是函數(shù)的類型姥敛。 函數(shù)類型的變量可以從函數(shù)中賦值,函數(shù)類型的函數(shù)參數(shù)可用于將函數(shù)傳遞給函數(shù)并從函數(shù)調(diào)用返回函數(shù)瞎暑。 函數(shù)類型有兩種類型——內(nèi)部函數(shù)和外部函數(shù): 內(nèi)部函數(shù)只能在當(dāng)前合約內(nèi)部調(diào)用(更具體地說彤敛,在當(dāng)前代碼單元內(nèi)部,也包括內(nèi)部庫函數(shù)和繼承函數(shù))了赌,因為它們不能在當(dāng)前合約的上下文之外執(zhí)行墨榄。 調(diào)用內(nèi)部函數(shù)是通過跳轉(zhuǎn)到其入口標(biāo)簽來實現(xiàn)的,就像在內(nèi)部調(diào)用當(dāng)前合約的函數(shù)一樣勿她。 外部函數(shù)由地址和函數(shù)簽名組成袄秩,它們可以通過外部函數(shù)調(diào)用傳遞和返回。 函數(shù)類型表示如下: ```solidity function () {internal|external} [pure|view|payable] [returns ()] ``` 與參數(shù)類型相反嫂拴,返回類型不能為空 - 如果函數(shù)類型不應(yīng)返回任何內(nèi)容播揪,則必須省略整個`returns ()`部分。 默認(rèn)情況下筒狠,函數(shù)類型是內(nèi)部的猪狈,因此可以省略`internal`關(guān)鍵字。 請注意辩恼,這僅適用于函數(shù)類型雇庙。 必須為合約中定義的函數(shù)明確指定可見性,它們沒有默認(rèn)值灶伊。 轉(zhuǎn)換: 函數(shù)類型 A 可隱式轉(zhuǎn)換為函數(shù)類型 B 當(dāng)且僅當(dāng)它們的參數(shù)類型相同疆前、返回類型相同、內(nèi)部/外部屬性相同并且 A 的狀態(tài)可變性比 B 的狀態(tài)可變性更具限制性 聘萨。 尤其: - `pure`函數(shù)可以轉(zhuǎn)換為`view`和`non-payable`函數(shù) - `view`函數(shù)可以轉(zhuǎn)換為`non-payable`函數(shù) - `payable`函數(shù)可以轉(zhuǎn)換為`non-payable`函數(shù) 函數(shù)類型之間沒有其他轉(zhuǎn)換是可能的竹椒。 關(guān)于`payable`和`non-payable`的規(guī)則可能有點混亂,但本質(zhì)上米辐,如果一個函數(shù)是`payable`的胸完,這意味著它也接受零以太幣的支付书释,所以它也是`non-payable`的。 另一方面赊窥,`non-payable`函數(shù)將拒絕發(fā)送給它的以太幣爆惧,因此`non-payable函數(shù)`無法轉(zhuǎn)換為`payable`。 澄清一下锨能,拒絕以太幣比不拒絕以太幣更具限制性扯再。 這意味著您可以用`non-payable`覆蓋`payable`,但反之則不行址遇。 此外熄阻,當(dāng)您定義一個`non-payable`函數(shù)指針時,編譯器不會強制指向的函數(shù)拒絕以太幣傲隶。 相反饺律,它強制函數(shù)指針永遠(yuǎn)不會用于發(fā)送以太幣窃页。 這使得將`payable`函數(shù)指針分配給`non-payable`函數(shù)指針成為可能跺株,確保兩種類型的行為方式相同,即兩者都不能用于發(fā)送以太幣脖卖。 如果未初始化函數(shù)類型變量乒省,則調(diào)用它會導(dǎo)致 Panic 錯誤。 如果在使用 `delete` 之后調(diào)用函數(shù)畦木,也會發(fā)生同樣的情況袖扛。 如果在 Solidity 上下文之外使用外部函數(shù)類型,它們將被視為函數(shù)類型十籍,它將地址后跟函數(shù)標(biāo)識符一起編碼為單個 bytes24 類型蛆封。 請注意,當(dāng)前合約的公共功能既可以用作內(nèi)部功能勾栗,也可以用作外部功能惨篱。 要將`f`用作內(nèi)部函數(shù),只需使用`f`围俘,如果要使用其外部形式砸讳,請使用`this.f`。 一個內(nèi)部類型的函數(shù)可以賦值給一個內(nèi)部函數(shù)類型的變量界牡,而不管它定義在哪里簿寂。 這包括合約和庫的私有、內(nèi)部和公共功能以及免費功能宿亡。 另一方面常遂,外部函數(shù)類型只與公共和外部合約函數(shù)兼容。 # 單位和全局變量 ## Ether單位 - `1 wei == 1` (默認(rèn)) - `1 gwei == 1e9` - `1 ether == 1e18` ## 時間單位 - `1?==?1?seconds` (默認(rèn)) - `1?minutes?==?60?seconds` - `1?hours?==?60?minutes` - `1?days?==?24?hours` - `1?weeks?==?7?days` ?? 由于閏秒造成的每年不都是 365 天挽荠、每天不都是 24 小時?[leap seconds](https://en.wikipedia.org/wiki/Leap_second) 克胳,所以如果你要使用這些單位計算日期和時間泊碑,請注意這個問題。因為閏秒是無法預(yù)測的毯欣,所以需要借助外部的預(yù)言機(oracle馒过,是一種鏈外數(shù)據(jù)服務(wù),譯者注)來對一個確定的日期代碼庫進(jìn)行時間矯正酗钞。 這些后綴不能直接用在變量后邊腹忽。如果想用時間單位(例如 days)來將輸入變量換算為時間,你可以用如下方式來完成: ```solidity function f(uint start, uint daysAfter) public { if (block.timestamp >= start + daysAfter * 1 days) { // ... } } ``` ## 特殊變量和函數(shù) 在全局命名空間中已經(jīng)存在了(預(yù)設(shè)了)一些特殊的變量和函數(shù)砚作,他們主要用來提供關(guān)于區(qū)塊鏈的信息或一些通用的工具函數(shù)窘奏。 ### ****區(qū)塊和交易屬性**** - `blockhash(uint?blockNumber)?returns?(bytes32)`:指定區(qū)塊的區(qū)塊哈希 —— 僅可用于最新的 256 個區(qū)塊且不包括當(dāng)前區(qū)塊,否則返回 0 葫录。 - `block.basefee`?(`uint`): 當(dāng)前區(qū)塊的基礎(chǔ)費用着裹,參考: ([EIP-3198](https://eips.ethereum.org/EIPS/eip-3198)?和?[EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)) - `block.chainid`?(`uint`): 當(dāng)前鏈 id - `block.coinbase`?(?`address`?): 挖出當(dāng)前區(qū)塊的礦工地址 - `block.difficulty`?(?`uint`?): 當(dāng)前區(qū)塊難度 - `block.gaslimit`?(?`uint`?): 當(dāng)前區(qū)塊 gas 限額 - `block.number`?(?`uint`?): 當(dāng)前區(qū)塊號 - `block.timestamp`?(?`uint`): 自 unix epoch 起始當(dāng)前區(qū)塊以秒計的時間戳 - `gasleft()?returns?(uint256)`?:剩余的 gas - `msg.data`?(?`bytes`?): 完整的 calldata - `msg.sender`?(?`address`?): 消息發(fā)送者(當(dāng)前調(diào)用) - `msg.sig`?(?`bytes4`?): calldata 的前 4 字節(jié)(也就是函數(shù)標(biāo)識符) - `msg.value`?(?`uint`?): 隨消息發(fā)送的 wei 的數(shù)量 - `tx.gasprice`?(`uint`): 交易的 gas 價格 - `tx.origin`?(?`address`?): 交易發(fā)起者(完全的調(diào)用鏈) ### ****ABI 編碼及解碼函數(shù)**** - `abi.decode(bytes?memory?encodedData,?(...))?returns?(...)`: 對給定的數(shù)據(jù)進(jìn)行ABI解碼,而數(shù)據(jù)的類型在括號中第二個參數(shù)給出 米同。 例如:?`(uint?a,?uint[2]?memory?b,?bytes?memory?c)?=?abi.decode(data,?(uint,?uint[2],?bytes))` - `abi.encode(...)?returns?(bytes)`:?[ABI](https://learnblockchain.cn/docs/solidity/abi-spec.html#abi)?- 對給定參數(shù)進(jìn)行編碼 - `abi.encodePacked(...)?returns?(bytes)`:對給定參數(shù)執(zhí)行?[緊打包編碼](https://learnblockchain.cn/docs/solidity/abi-spec.html#abi-packed-mode)?骇扇,注意,可以不明確打包編碼面粮。 - `abi.encodeWithSelector(bytes4?selector,?...)?returns?(bytes)`:?[ABI](https://learnblockchain.cn/docs/solidity/abi-spec.html#abi)?- 對給定第二個開始的參數(shù)進(jìn)行編碼少孝,并以給定的函數(shù)選擇器作為起始的 4 字節(jié)數(shù)據(jù)一起返回 - `abi.encodeWithSignature(string?signature,?...)?returns?(bytes)`:等價于?`abi.encodeWithSelector(bytes4(keccak256(signature),?...)` - `abi.encodeCall(function?functionPointer,?(...))?returns?(bytes?memory)`: 使用tuple類型參數(shù)ABI 編碼調(diào)用?`functionPointer`?。執(zhí)行完整的類型檢查, 確保類型匹配函數(shù)簽名熬苍。結(jié)果和?`abi.encodeWithSelector(functionPointer.selector,?(...))`?一致稍走。 ?? 這些編碼函數(shù)可以用來構(gòu)造函數(shù)調(diào)用數(shù)據(jù),而不用實際進(jìn)行調(diào)用柴底。此外婿脸,`keccak256(abi.encodePacked(a,?b))` ?是一種計算結(jié)構(gòu)化數(shù)據(jù)的哈希值(盡管我們也應(yīng)該關(guān)注到:使用不同的函數(shù)參數(shù)類型也有可能會引起“哈希沖突” )的方式,不推薦使用的?`keccak256(a,?b)` ?柄驻。 ### 錯誤處理 - **`assert(bool?condition)`**如果不滿足條件狐树,則會導(dǎo)致Panic 錯誤,則撤銷狀態(tài)更改 - 用于檢查內(nèi)部錯誤凿歼。 - **`require(bool?condition)`**如果條件不滿足則撤銷狀態(tài)更改 - 用于檢查由輸入或者外部組件引起的錯誤褪迟。 - **`require(bool?condition,?string?memory?message)`**如果條件不滿足則撤銷狀態(tài)更改 - 用于檢查由輸入或者外部組件引起的錯誤,可以同時提供一個錯誤消息答憔。 - **`revert()`**終止運行并撤銷狀態(tài)更改味赃。 - **`revert(string?memory?reason)`**終止運行并撤銷狀態(tài)更改,可以同時提供一個解釋性的字符串虐拓。 ### ****數(shù)學(xué)和密碼學(xué)函數(shù)**** - **`addmod(uint?x,?uint?y,?uint?k)?returns?(uint)`**計算?`(x?+?y)?%?k`心俗,加法會在任意精度下執(zhí)行,并且加法的結(jié)果即使超過?`2**256`?也不會被截取。從 0.5.0 版本的編譯器開始會加入對?`k?!=?0`?的校驗(assert)城榛。 - **`mulmod(uint?x,?uint?y,?uint?k)?returns?(uint)`**計算?`(x?*?y)?%?k`揪利,乘法會在任意精度下執(zhí)行,并且乘法的結(jié)果即使超過?`2**256`?也不會被截取狠持。從 0.5.0 版本的編譯器開始會加入對?`k?!=?0`?的校驗(assert)疟位。 - **`keccak256((bytes?memory)?returns?(bytes32)`**計算 Keccak-256 哈希。 - **`sha256(bytes?memory)?returns?(bytes32)`**計算參數(shù)的 SHA-256 哈希喘垂。 - **`ripemd160(bytes?memory)?returns?(bytes20)`**計算參數(shù)的 RIPEMD-160 哈希甜刻。 - **`ecrecover(bytes32?hash,?uint8?v,?bytes32?r,?bytes32?s)?returns?(address)`**利用橢圓曲線簽名恢復(fù)與公鑰相關(guān)的地址,錯誤返回零值正勒。 函數(shù)參數(shù)對應(yīng)于 ECDSA簽名的值: ? `r`?= 簽名的前 32 字節(jié) ? `s`?= 簽名的第2個32 字節(jié) ? `v`?= 簽名的最后一個字節(jié) `ecrecover`?返回一個?`address`, 而不是?`address?payable`?得院。他們之前的轉(zhuǎn)換參考?[address payable](https://learnblockchain.cn/docs/solidity/types.html#address)?,如果需要轉(zhuǎn)移資金到恢復(fù)的地址章贞。 ### 地址成員 - **` .balance`?(`uint256`)**以 Wei 為單位的?[地址類型 Address](https://learnblockchain.cn/docs/solidity/types.html#address)?的余額祥绞。 - **` .code`?(`bytes?memory`)**在?[地址類型 Address](https://learnblockchain.cn/docs/solidity/types.html#address)?上的代碼(可以為空) - **` .codehash`?(`bytes32`)**[地址類型 Address](https://learnblockchain.cn/docs/solidity/types.html#address)?的codehash - **`.transfer(uint256?amount)`**向?[地址類型 Address](https://learnblockchain.cn/docs/solidity/types.html#address)?發(fā)送數(shù)量為 amount 的 Wei,失敗時拋出異常鸭限,使用固定(不可調(diào)節(jié))的 2300 gas 的礦工費蜕径。 - **`.send(uint256?amount)?returns?(bool)`**向?[地址類型 Address](https://learnblockchain.cn/docs/solidity/types.html#address)?發(fā)送數(shù)量為 amount 的 Wei,失敗時返回?`false`里覆,發(fā)送 2300 gas 的礦工費用丧荐,不可調(diào)節(jié)。 - **` .call(bytes?memory)?returns?(bool,?bytes?memory)`**用給定的有效載荷(payload)發(fā)出低級?`CALL`?調(diào)用喧枷,返回成功狀態(tài)及返回數(shù)據(jù),發(fā)送所有可用 gas弓坞,也可以調(diào)節(jié) gas隧甚。 - **` .delegatecall(bytes?memory)?returns?(bool,?bytes?memory)`**用給定的有效載荷 發(fā)出低級?`DELEGATECALL`?調(diào)用 ,返回成功狀態(tài)并返回數(shù)據(jù)渡冻,發(fā)送所有可用 gas戚扳,也可以調(diào)節(jié) gas。 發(fā)出低級函數(shù)?`DELEGATECALL`族吻,失敗時返回?`false`帽借,發(fā)送所有可用 gas,可調(diào)節(jié)超歌。 - **` .staticcall(bytes?memory)?returns?(bool,?bytes?memory)`**用給定的有效載荷 發(fā)出低級?`STATICCALL`?調(diào)用 砍艾,返回成功狀態(tài)并返回數(shù)據(jù),發(fā)送所有可用 gas巍举,也可以調(diào)節(jié) gas脆荷。 ### 合約相關(guān) - **`this`?(當(dāng)前的合約類型)**當(dāng)前合約,可以顯示轉(zhuǎn)換為?[地址類型 Address](https://learnblockchain.cn/docs/solidity/types.html#address)。 - **`selfdestruct(address?payable?recipient)`**銷毀合約蜓谋,并把余額發(fā)送到指定?[地址類型 Address](https://learnblockchain.cn/docs/solidity/types.html#address)梦皮。請注意,?`selfdestruct`?具有從EVM繼承的一些特性: - 接收合約的 receive 函數(shù) 不會執(zhí)行桃焕。 ??- - 合約僅在交易結(jié)束時才真正被銷毀剑肯,并且?`revert`?可能會“撤消”銷毀。 此外观堂,當(dāng)前合約內(nèi)的所有函數(shù)都可以被直接調(diào)用退子,包括當(dāng)前函數(shù)。 # 表達(dá)式和控制結(jié)構(gòu) ## 控制結(jié)構(gòu) JavaScript 中的大部分控制結(jié)構(gòu)在 Solidity 中都是可用的型将,除了?`switch`?和?`goto`寂祥。 因此 Solidity 中有?`if`,?`else`七兜,?`while`丸凭,?`do`,?`for`腕铸,?`break`惜犀,?`continue`,?`return`狠裹,?`??:`?這些與在 C 或者 JavaScript 中表達(dá)相同語義的關(guān)鍵詞御铃。 Solidity還支持?`try`/?`catch`?語句形式的異常處理,但僅用于?[外部函數(shù)調(diào)用](https://learnblockchain.cn/docs/solidity/control-structures.html#external-function-calls) 和合約創(chuàng)建調(diào)用泻轰。 使用revert 語句 ?可以觸發(fā)一個”錯誤”疙渣。 用于表示條件的括號?*不可以*?被省略,單語句體兩邊的花括號可以被省略俗冻。 注意礁叔,與 C 和 JavaScript 不同, Solidity 中非布爾類型數(shù)值不能轉(zhuǎn)換為布爾類型迄薄,因此?`if?(1)?{?...?}`?的寫法在 Solidity 中?*無效*?琅关。 ## 函數(shù)調(diào)用 ### 內(nèi)部函數(shù)調(diào)用 當(dāng)前合約中的函數(shù)可以直接(“從內(nèi)部”)調(diào)用,也可以遞歸調(diào)用讥蔽,就像下邊這個無意義的例子一樣: ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.22 <0.9.0; // 編譯器會有警告提示 contract C { function g(uint a) public pure returns (uint ret) { return f(); } function f() internal pure returns (uint ret) { return g(7) + f(); } } ``` ![](https://upload-images.jianshu.io/upload_images/6687907-e9ea38290c539bc5.png) 這些函數(shù)調(diào)用在 EVM 中被解釋為簡單的跳轉(zhuǎn)涣易。這樣做的效果就是當(dāng)前內(nèi)存不會被清除,例如冶伞,函數(shù)之間通過傳遞內(nèi)存引用進(jìn)行內(nèi)部調(diào)用是非常高效的新症。 只能在同一合約實例的函數(shù),可以進(jìn)行內(nèi)部調(diào)用碰缔。 只有在同一合約的函數(shù)可以內(nèi)部調(diào)用账劲。仍然應(yīng)該避免過多的遞歸調(diào)用, 因為每個內(nèi)部函數(shù)調(diào)用至少使用一個堆棧槽, 并且最多有1024堆棧槽可用。 ### 外部函數(shù)調(diào)用 可以使用表達(dá)式?`this.g(8);`?和?`c.g(2);`?進(jìn)行調(diào)用,其中?`c`?是合約實例瀑焦,?`g`?合約內(nèi)實現(xiàn)的函數(shù)腌且,但是這兩種方式調(diào)用函數(shù),稱為“外部調(diào)用”榛瓮,它是通過消息調(diào)用來進(jìn)行铺董,而不是直接的代碼跳轉(zhuǎn)。 請注意禀晓,不可以在構(gòu)造函數(shù)中通過 this 來調(diào)用函數(shù)精续,因為此時真實的合約實例還沒有被創(chuàng)建。 如果想要調(diào)用其他合約的函數(shù)粹懒,需要外部調(diào)用重付。對于一個外部調(diào)用,所有的函數(shù)參數(shù)都需要被復(fù)制到內(nèi)存凫乖。 ?? 從一個合約到另一個合約的函數(shù)調(diào)用不會創(chuàng)建自己的交易, 它是作為整個交易的一部分的消息調(diào)用确垫。 當(dāng)調(diào)用其他合約的函數(shù)時,需要在函數(shù)調(diào)用是指定發(fā)送的 Wei 和 gas 數(shù)量帽芽,可以使用特定選項 `{value:?10,?gas:?10000}` 請注意删掀,不建議明確指定gas,因為操作碼的 gas 消耗將來可能會發(fā)生變化导街。 任何發(fā)送給合約 Wei 將被添加到目標(biāo)合約的總余額中: ```solidity pragma solidity >=0.6.2 <0.9.0; contract InfoFeed { function info() public payable returns (uint ret) { return 42; } } contract Consumer { InfoFeed feed; function setFeed(InfoFeed addr) public { feed = addr; } function callFeed() public { feed.info{value: 10, gas: 800}(); } } ``` `payable`?修飾符要用于修飾?`info`?函數(shù)披泪,否則,?`value`?選項將不可用搬瑰。 由于EVM認(rèn)為可以調(diào)用不存在的合約的調(diào)用款票,因此在 Solidity 語言層面里會使用?`extcodesize`?操作碼來檢查要調(diào)用的合約是否確實存在(包含代碼),如果不存在該合約跌捆,則拋出異常徽职。 如果返回數(shù)據(jù)在調(diào)用后被解碼,則跳過這個檢查佩厚,因此ABI解碼器將捕捉到不存在的合約的情況。 請注意说订,這個檢查在?[低級別調(diào)用](https://learnblockchain.cn/docs/solidity/units-and-global-variables.html#address-related)?時不被執(zhí)行抄瓦,這些調(diào)用是對地址而不是合約實例進(jìn)行操作。 如果被調(diào)用合約本身拋出異程绽洌或者 gas 用完等钙姊,函數(shù)調(diào)用也會拋出異常。 ?? 任何與其他合約的交互都會產(chǎn)生潛在危險埂伦,尤其是在不能預(yù)先知道合約代碼的情況下煞额。 交互時當(dāng)前合約會將控制權(quán)移交給被調(diào)用合約,而被調(diào)用合約可能做任何事。即使被調(diào)用合約從一個已知父合約繼承膊毁,繼承的合約也只需要有一個正確的接口就可以了胀莹。 被調(diào)用合約的實現(xiàn)可以完全任意的實現(xiàn),因此會帶來危險婚温。 此外描焰,請小心這個交互調(diào)用在返回之前再回調(diào)我們的合約,這意味著被調(diào)用合約可以通過它自己的函數(shù)改變調(diào)用合約的狀態(tài)變量栅螟。 一個建議的函數(shù)寫法是荆秦,例如吃媒,在合約中狀態(tài)變量進(jìn)行各種變化后再調(diào)用外部函數(shù)瓤介,這樣,你的合約就不會輕易被濫用的重入攻擊 (reentrancy) 所影響 ### ****具名參數(shù)函數(shù)調(diào)用**** 函數(shù)調(diào)用參數(shù)可以按名稱以任何順序給出晓折,如果它們包含在 {} 中惑朦,如以下示例所示。 參數(shù)列表的名稱必須與函數(shù)聲明中的參數(shù)列表一致胃珍,但順序可以是任意的梁肿。 ```solidity pragma solidity >=0.4.0 <0.9.0; contract C { mapping(uint => uint) data; function f() public { set({value: 2, key: 3}); } function set(uint key, uint value) public { data[key] = value; } } ``` ### ****省略函數(shù)參數(shù)名稱**** 函數(shù)聲明中的參數(shù)名稱和返回值可以省略。 那些省略名稱的項目仍會出現(xiàn)在堆棧中觅彰,但無法通過名稱訪問它們吩蔑。 省略的返回值名稱仍然可以通過使用 return 語句將值返回給調(diào)用者。 ```solidity pragma solidity >=0.4.22 <0.9.0; contract C { // 省略參數(shù)名稱 function func(uint k, uint) public pure returns(uint) { return k; } } ``` ## ****通過?`new`?創(chuàng)建合約** 使用關(guān)鍵字?`new`?可以創(chuàng)建一個新合約填抬。待創(chuàng)建合約的完整代碼必須事先知道烛芬,因此遞歸的創(chuàng)建依賴是不可能的。 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract D { uint public x; constructor(uint a) payable { x = a; } } contract C { D d = new D(4); // will be executed as part of C's constructor function createD(uint arg) public { D newD = new D(arg); newD.x(); } function createAndEndowD(uint arg, uint amount) public payable { // Send ether along with the creation D newD = new D{value: amount}(arg); newD.x(); } } ``` 如示例中所示飒责,可以在使用 value 選項創(chuàng)建 D 的實例時發(fā)送 Ether赘娄,但無法限制gas的數(shù)量。 如果創(chuàng)建失敽牝取(由于出棧遣臼、余額不足或其他問題),則拋出異常拾并。 ### 加鹽合約創(chuàng)建/create2 創(chuàng)建合約時揍堰,合約的地址是根據(jù)創(chuàng)建合約的地址和一個隨著每次合約創(chuàng)建而增加的計數(shù)器計算得出的鹏浅。 如果您指定選項`salt`(bytes32 值),那么合約創(chuàng)建將使用不同的機制來提供新合約的地址: 它將根據(jù)創(chuàng)建合約的地址屏歹、給定的`salt`值隐砸、已創(chuàng)建合約的(創(chuàng)建)字節(jié)碼和構(gòu)造函數(shù)參數(shù)計算地址。 特別是西采,不使用計數(shù)器(“nonce”)凰萨。 這允許在創(chuàng)建合同時更加靈活:您可以在創(chuàng)建新合同之前派生出新合同的地址。 此外械馆,如果創(chuàng)建合約同時創(chuàng)建其他合約胖眷,您也可以依賴此地址。 一個主要用例場景是充當(dāng)鏈下交互仲裁合約霹崎,僅在有爭議時才需要創(chuàng)建珊搀。 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract D { uint public x; constructor(uint a) { x = a; } } contract C { function createDSalted(bytes32 salt, uint arg) public { // This complicated expression just tells you how the address // can be pre-computed. It is just there for illustration. // You actually only need ``new D{salt: salt}(arg)``. address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(abi.encodePacked( type(D).creationCode, abi.encode(arg) )) ))))); D d = new D{salt: salt}(arg); require(address(d) == predictedAddress); } } ``` ?? 使用 create2 創(chuàng)建合約還有一些特別之處。 合約銷毀后可以在同一地址重新創(chuàng)建尾菇。不過境析,即使創(chuàng)建字節(jié)碼(creation bytecode)相同(這是要求,因為否則地址會發(fā)生變化)派诬,該新創(chuàng)建的合約也可能有不同的部署字節(jié)碼(deployed bytecode)劳淆。 這是因為構(gòu)造函數(shù)可以使用兩次創(chuàng)建合約之間可能已更改的外部狀態(tài),并在存儲合約時將其合并到部署字節(jié)碼中默赂。 ## 賦值 ### ****解構(gòu)賦值和返回多值**** Solidity 內(nèi)部允許元組 (tuple) 類型沛鸵,也就是一個在編譯時元素數(shù)量固定的對象列表,列表中的元素可以是不同類型的對象缆八。這些元組可以用來同時返回多個數(shù)值曲掰,也可以用它們來同時給多個新聲明的變量或者既存的變量(或通常的 LValues)賦值: ```solidity pragma solidity >=0.5.0 <0.9.0; contract C { uint index; function f() public pure returns (uint, bool, uint) { return (7, true, 2); } function g() public { //基于返回的元組來聲明變量并賦值 (uint x, bool b, uint y) = f(); //交換兩個值的通用竅門——但不適用于非值類型的存儲 (storage) 變量。 (x, y) = (y, x); //元組的末尾元素可以省略(這也適用于變量聲明)奈辰。 (index,,) = f(); // 設(shè)置 index 為 7 } } ``` 不可能混合變量聲明和非聲明變量復(fù)制, 即以下是無效的:?`(x,?uint?y)?=?(1,?2);` ### ****數(shù)組和結(jié)構(gòu)體的復(fù)雜性**** 賦值語義對于像數(shù)組和結(jié)構(gòu)體(包括?`bytes`?和?`string`) 這樣的非值類型來說會有些復(fù)雜栏妖。 在下面的示例中, 對?`g(x)`?的調(diào)用對?`x`?沒有影響, 因為它在內(nèi)存中創(chuàng)建了存儲值獨立副本。但是,?`h(x)`成功修改?`x`?, 因為只傳遞引用而不傳遞副本奖恰。 ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.22 <0.9.0; contract C { uint[20] x; function f() public { g(x); h(x); } function g(uint[20] memory y) internal pure { y[2] = 3; } function h(uint[20] storage y) internal { y[3] = 4; } } ``` ## ****作用域和聲明**** 變量聲明后將有默認(rèn)初始值吊趾,其初始值字節(jié)表示全部為零。任何類型變量的“默認(rèn)值”是其對應(yīng)類型的典型“零狀態(tài)”瑟啃。例如趾徽,?`bool`?類型的默認(rèn)值是?`false`?。?`uint`?或?`int`?類型的默認(rèn)值是?`0`?翰守。對于靜態(tài)大小的數(shù)組和?`bytes1`?到?`bytes32`?,每個單獨的元素將被初始化為與其類型相對應(yīng)的默認(rèn)值疲酌。 最后蜡峰,對于動態(tài)大小的數(shù)組?`bytes`?和?`string`?類型了袁,其默認(rèn)缺省值是一個空數(shù)組或空字符串。 對于?`enum`?類型, 默認(rèn)值是第一個成員湿颅。 變量聲明后將有默認(rèn)初始值载绿,其初始值字節(jié)表示全部為零。任何類型變量的“默認(rèn)值”是其對應(yīng)類型的典型“零狀態(tài)”油航。例如崭庸,?`bool`?類型的默認(rèn)值是?`false`?。?`uint`?或?`int`?類型的默認(rèn)值是?`0`?谊囚。對于靜態(tài)大小的數(shù)組和?`bytes1`?到?`bytes32`?怕享,每個單獨的元素將被初始化為與其類型相對應(yīng)的默認(rèn)值。 最后镰踏,對于動態(tài)大小的數(shù)組?`bytes`?和?`string`?類型函筋,其默認(rèn)缺省值是一個空數(shù)組或空字符串。 對于?`enum`?類型, 默認(rèn)值是第一個成員奠伪。 Solidity 中的作用域規(guī)則遵循了 C99(與其他很多語言一樣):變量將會從它們被聲明之后可見跌帐,直到一對?`{?}`?塊的結(jié)束。作為一個例外绊率,在 for 循環(huán)語句中初始化的變量谨敛,其可見性僅維持到 for 循環(huán)的結(jié)束。 對于參數(shù)形式的變量(例如:函數(shù)參數(shù)滤否、修飾器參數(shù)脸狸、catch參數(shù)等等)在其后接著的代碼塊內(nèi)有效。 這些代碼塊是函數(shù)的實現(xiàn)顽聂,catch 語句塊等肥惭。 那些定義在代碼塊之外的變量,比如函數(shù)紊搪、合約蜜葱、自定義類型等等,并不會影響它們的作用域特性耀石。這意味著你可以在實際聲明狀態(tài)變量的語句之前就使用它們牵囤,并且遞歸地調(diào)用函數(shù)。 基于以上的規(guī)則滞伟,下邊的例子不會出現(xiàn)編譯警告揭鳞,因為那兩個變量雖然名字一樣,但卻在不同的作用域里梆奈。 ```solidity pragma solidity >=0.5.0 <0.9.0; contract C { function minimalScoping() pure public { { uint same; same = 1; } { uint same; same = 3; } } } ``` 作為 C99 作用域規(guī)則的特例野崇,請注意在下邊的例子里,第一次對?`x` ?的賦值會改變上一層中聲明的變量值亩钟。如果外層聲明的變量被“覆蓋”(就是說被在內(nèi)部作用域中由一個同名變量所替代)你會得到一個警告乓梨。 ```solidity pragma solidity >=0.5.0 <0.9.0; // 有警告 contract C { function f() pure public returns (uint) { uint x = 1; { x = 2; // 這個賦值會影響在外層聲明的變量 uint x; } return x; // x has value 2 } } ``` # ****算術(shù)運算的檢查模式與非檢查模式**** 當(dāng)對無限制整數(shù)執(zhí)行算術(shù)運算鳖轰,其結(jié)果超出結(jié)果類型的范圍,這是就發(fā)生了上溢出或下溢出扶镀。 在Solidity 0.8.0之前蕴侣,算術(shù)運算總是會在發(fā)生溢出的情況下進(jìn)行“截斷”,從而得靠引入額外檢查庫來解決這個問題(如 OpenZepplin 的 SafeMath)臭觉。 而從Solidity 0.8.0開始昆雀,所有的算術(shù)運算默認(rèn)就會進(jìn)行溢出檢查,額外引入庫將不再必要蝠筑。 如果想要之前“截斷”的效果狞膘,可以使用?`unchecked`?代碼塊: ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract C { function f(uint a, uint b) pure public returns (uint) { // 減法溢出會返回“截斷”的結(jié)果 unchecked { return a - b; } } function g(uint a, uint b) pure public returns (uint) { // 溢出會拋出異常 return a - b; } } ``` 調(diào)用?`f(2,?3)`?將返回?`2**256-1`, 而?`g(2,?3)`?會觸發(fā)失敗異常。 `unchecked`?代碼塊可以在代碼塊中的任何位置使用菱肖,但不可以替代整個函數(shù)代碼塊客冈,同樣不可以嵌套。 此設(shè)置僅影響語法上位于?`unchecked`?塊內(nèi)的語句稳强。 在塊中調(diào)用的函數(shù)不會此影響场仲。 # ****錯誤處理及異常:Assert, Require, Revert**** Solidity 使用狀態(tài)恢復(fù)異常來處理錯誤。這種異常將撤消對當(dāng)前調(diào)用(及其所有子調(diào)用)中的狀態(tài)所做的所有更改退疫,并且還向調(diào)用者標(biāo)記錯誤渠缕。 如果異常在子調(diào)用發(fā)生,那么異常會自動冒泡到頂層(例如:異常會重新拋出)褒繁,除非他們在?`try/catch`?語句中捕獲了錯誤亦鳞。 但是如果是在?`send`?和 低級別如:?`call`,?`delegatecall`?和?`staticcall`?的調(diào)用里發(fā)生異常時, 他們會返回?`false`?(第一個返回值) 而不是冒泡異常棒坏。 異逞嗖睿可以包含錯誤數(shù)據(jù),以?[error 示例](https://learnblockchain.cn/docs/solidity/contracts.html#errors) ?的形式傳回給調(diào)用者坝冕。 內(nèi)置的錯誤?`Error(string)` ?和?`Panic(uint256)` ?被作為特殊函數(shù)使用徒探,下面將解釋。?`Error` ?用于 “常規(guī)” 錯誤條件喂窟,而?`Panic` ?用于在(無bug)代碼中不應(yīng)該出現(xiàn)的錯誤测暗。 ## ****用?`assert`?檢查異常(Panic) 和?`require`?檢查錯誤(Error)** 函數(shù)?`assert`?和?`require`?可用于檢查條件并在條件不滿足時拋出異常。 `assert`?函數(shù)會創(chuàng)建一個?`Panic(uint256)`?類型的錯誤磨澡。 同樣的錯誤在以下列出的特定情形會被編譯器創(chuàng)建碗啄。 `assert`?函數(shù)應(yīng)該只用于測試內(nèi)部錯誤,檢查不變量稳摄,正常的函數(shù)代碼永遠(yuǎn)不會產(chǎn)生Panic, 甚至是基于一個無效的外部輸入時稚字。 如果發(fā)生了,那就說明出現(xiàn)了一個需要你修復(fù)的 bug厦酬。如果使用得當(dāng)尉共,語言分析工具可以識別出那些會導(dǎo)致 Panic 的?`assert`?條件和函數(shù)調(diào)用褒傅。 下列情況將會產(chǎn)生一個Panic異常: 錯誤數(shù)據(jù)會提供的錯誤碼編號,用來指示Panic的類型: 1. 0x00: 用于常規(guī)編譯器插入的Panic袄友。 2. 0x01: 如果你調(diào)用?`assert`?的參數(shù)(表達(dá)式)結(jié)果為 false 。 3. 0x11: 在?`unchecked?{?...?}`?外霹菊,如果算術(shù)運算結(jié)果向上或向下溢出剧蚣。 4. 0x12; 如果你用零當(dāng)除數(shù)做除法或模運算(例如?`5?/?0`?或?`23?%?0`?)。 5. 0x21: 如果你將一個太大的數(shù)或負(fù)數(shù)值轉(zhuǎn)換為一個枚舉類型旋廷。 6. 0x22: 如果你訪問一個沒有正確編碼的存儲byte數(shù)組. 7. 0x31: 如果在空數(shù)組上?`.pop()`?鸠按。 8. 0x32: 如果你訪問?`bytesN`?數(shù)組(或切片)的索引太大或為負(fù)數(shù)。(例如:?`x[i]`?而?`i?>=?x.length`?或?`i?<?0`). 9. 0x41: 如果你分配了太多的內(nèi)內(nèi)存或創(chuàng)建了太大的數(shù)組饶碘。 10. 0x51: 如果你調(diào)用了零初始化內(nèi)部函數(shù)類型變量目尖。 `require`函數(shù)可以創(chuàng)建無錯誤提示的錯誤,也可以創(chuàng)建一個?`Error(string)`類型的錯誤扎运。?`require`函數(shù)應(yīng)該用于確認(rèn)條件有效性瑟曲,例如輸入變量,或合約狀態(tài)變量是否滿足條件豪治,或驗證外部合約調(diào)用返回的值洞拨。 下列情況將會產(chǎn)生一個?`Error(string)`?(或無錯誤提示)的錯誤: 1. 如果你調(diào)用?`require(x)`?,而?`x`?結(jié)果為?`false`?负拟。 2. 如果你使用?`revert()`?或者?`revert("description")`?烦衣。 3. 如果你在不包含代碼的合約上執(zhí)行外部函數(shù)調(diào)用。 4. 如果你通過合約接收以太幣掩浙,而又沒有?`payable`?修飾符的公有函數(shù)(包括構(gòu)造函數(shù)和 fallback 函數(shù))花吟。 5. 如果你的合約通過公有 getter 函數(shù)接收 Ether 。 在下面的情況下厨姚,來自外部調(diào)用的錯誤數(shù)據(jù)(如果提供的話)被轉(zhuǎn)發(fā)衅澈,這意味可能?Error?或?Panic?都有可能觸發(fā)。 1. 如果?`.transfer()`?失敗遣蚀。 2. 如果你通過消息調(diào)用調(diào)用某個函數(shù)矾麻,但該函數(shù)沒有正確結(jié)束(例如, 它耗盡了 gas,沒有匹配函數(shù)芭梯,或者本身拋出一個異常)险耀,不包括使用低級別?`call`?,?`send`?玖喘,?`delegatecall`?甩牺,?`callcode`?或?`staticcall`?的函數(shù)調(diào)用。低級操作不會拋出異常累奈,而通過返回?`false`?來指示失敗贬派。 3. 如果您使用 `new` 關(guān)鍵字創(chuàng)建合約急但,但合約創(chuàng)建沒有正確完成。 你可以選擇給?`require`提供一個消息字符串搞乏,但?`assert`不行波桩。 ?? 如果你沒有為?`require`提供一個字符串參數(shù),它會用空錯誤數(shù)據(jù)進(jìn)行 revert请敦, 甚至不包括錯誤選擇器镐躲。 在下例中,你可以看到如何輕松使用?`require`檢查輸入條件以及如何使用?`assert`檢查內(nèi)部錯誤. ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.5.0 <0.9.0; contract Sharer { function sendHalf(address addr) public payable returns (uint balance) { require(msg.value % 2 == 0, "Even value required."); uint balanceBeforeTransfer = this.balance; addr.transfer(msg.value / 2); // 由于轉(zhuǎn)賬函數(shù)在失敗時拋出異常并且不會調(diào)用到以下代碼侍筛,因此我們應(yīng)該沒有辦法檢查仍然有一半的錢萤皂。 assert(this.balance == balanceBeforeTransfer - msg.value / 2); return this.balance; } } ``` 在內(nèi)部, Solidity 對異常執(zhí)行回退操作(指令?`0xfd`?)匣椰,從而讓 EVM 回退對狀態(tài)所做的所有更改裆熙。回退的原因是無法安全地繼續(xù)執(zhí)行禽笑,因為無法達(dá)到預(yù)期的結(jié)果入录。 因為我們想要保持交易的原子性,最安全的動作是回退所有的更改蒲每,并讓整個交易(或至少調(diào)用)沒有任何新影響纷跛。 在這兩種情況下,調(diào)用者都可以使用?`try`/?`catch`?來應(yīng)對此類失敗邀杏,但是被調(diào)用函數(shù)的更改將始終被還原贫奠。 ### revert 可以使用?`revert`?語句和?`revert`?函數(shù)來直接觸發(fā)回退。 `revert`?語句將一個自定義的錯誤作為直接參數(shù)望蜡,沒有括號: > revert CustomError(arg1, arg2); > 由于向后兼容唤崭,還有一個?`revert()`?函數(shù),它使用圓括號接受一個字符串: > revert(); revert(“description”); > 錯誤數(shù)據(jù)將被傳回給調(diào)用者脖律,以便在那里捕獲到錯誤數(shù)據(jù)谢肾。 使用?`revert()`?會觸發(fā)一個沒有任何錯誤數(shù)據(jù)的回退,而?`revert("description")`?會產(chǎn)生一個?`Error(string)`?錯誤小泉。 **使用一個自定義的錯誤實例通常會比字符串描述便宜得多芦疏。因為你可以使用錯誤名來描述它,它只被編碼為四個字節(jié)微姊。更長的描述可以通過NatSpec提供酸茴,這不會產(chǎn)生任何費用。** 下面的例子顯示了如何使用一個錯誤字符串和一個自定義錯誤實例兢交,他們和?`revert`?或相應(yīng)的?`require`?一起使用薪捍。 ```solidity contract VendingMachine { address owner; error Unauthorized(); function buy(uint amount) public payable { if (amount > msg.value / 2 ether) revert("Not enough Ether provided."); // 另一個可選的方式: require( amount <= msg.value / 2 ether, "Not enough Ether provided." ); // 以下執(zhí)行購買邏輯 } function withdraw() public { if (msg.sender != owner) revert Unauthorized(); payable(msg.sender).transfer(address(this).balance); } } ``` 只要參數(shù)沒有額外的附加效果,使用?`if?(!condition)?revert(...);`和?`require(condition,?...);`是等價的,例如當(dāng)參數(shù)是字符串的情況酪穿。 ?? `require`是一個像其他函數(shù)一樣可被執(zhí)行的函數(shù)凳干。 意味著,所有的參數(shù)在函數(shù)被執(zhí)行之前就都會被執(zhí)行被济。 尤其救赐,在?`require(condition,?f())`里,函數(shù)?`f`會被執(zhí)行溉潭,即便?`condition`為 True . 如果是調(diào)用?`Error(string)`函數(shù)净响,這里提供的字符串將經(jīng)過?[ABI 編碼](https://learnblockchain.cn/docs/solidity/abi-spec.html#abi)。 在上邊的例子里喳瓣,?`revert("Not?enough?Ether?provided.");` 會產(chǎn)生如下的十六進(jìn)制錯誤返回值: ```solidity 0x08c379a0 // Error(string) 的函數(shù)選擇器 0x0000000000000000000000000000000000000000000000000000000000000020 // 數(shù)據(jù)的偏移量(32) 0x000000000000000000000000000000000000000000000000000000000000001a // 字符串長度(26) 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // 字符串?dāng)?shù)據(jù)("Not enough Ether provided." 的 ASCII 編碼,26字節(jié)) ``` 提示信息可以通過?`try/catch`?(下面介紹)來獲取到赞别。 ### ****`try/catch`**** 外部調(diào)用的失敗畏陕,可以通過 try/catch 語句來捕獲,例如: ```solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.1; interface DataFeed { function getData(address token) external returns (uint value); } contract FeedConsumer { DataFeed feed; uint errorCount; function rate(address token) public returns (uint value, bool success) { // 如果錯誤超過 10 次仿滔,永久關(guān)閉這個機制 require(errorCount < 10); try feed.getData(token) returns (uint v) { return (v, true); } catch Error(string memory /*reason*/) { // This is executed in case // revert was called inside getData // and a reason string was provided. errorCount++; return (0, false); } catch Panic(uint /*errorCode*/) { // This is executed in case of a panic, // i.e. a serious error like division by zero // or overflow. The error code can be used // to determine the kind of error. errorCount++; return (0, false); } catch (bytes memory /*lowLevelData*/) { // This is executed in case revert() was used惠毁。 errorCount++; return (0, false); } } } ``` `try`關(guān)鍵詞后面必須有一個表達(dá)式,代表外部函數(shù)調(diào)用或合約創(chuàng)建(?`new?ContractName()`)崎页。 在表達(dá)式上的錯誤不會被捕獲(例如鞠绰,如果它是一個復(fù)雜的表達(dá)式,還涉及內(nèi)部函數(shù)調(diào)用)飒焦,只有外部調(diào)用本身發(fā)生的revert 可以捕獲蜈膨。 接下來的?`returns`?部分(是可選的)聲明了與外部調(diào)用返回的類型相匹配的返回變量。 在沒有錯誤的情況下牺荠,這些變量被賦值翁巍,合約將繼續(xù)執(zhí)行第一個成功塊內(nèi)代碼。 如果到達(dá)成功塊的末尾休雌,則在?`catch`?塊之后繼續(xù)執(zhí)行灶壶。 Solidity 根據(jù)錯誤的類型,支持不同種類的捕獲代碼塊: - `catch?Error(string?memory?reason)?{?...?}`: 如果錯誤是由?`revert("reasonString")`?或?`require(false,?"reasonString")`?(或?qū)е逻@種異常的內(nèi)部錯誤)引起的杈曲,則執(zhí)行這個catch子句驰凛。 - `catch?Panic(uint?errorCode)?{?...?}`: 如果錯誤是由 panic 引起的(如:?`assert`?失敗,除以0担扑,無效的數(shù)組訪問恰响,算術(shù)溢出等),將執(zhí)行這個catch子句魁亦。 - `catch?(bytes?memory?lowLevelData)?{?...?}`: 如果錯誤簽名不符合任何其他子句渔隶,如果在解碼錯誤信息時出現(xiàn)了錯誤,或者如果異常沒有一起提供錯誤數(shù)據(jù)。在這種情況下间唉,子句聲明的變量提供了對低級錯誤數(shù)據(jù)的訪問绞灼。 - `catch?{?...?}`: 如果你對錯誤數(shù)據(jù)不感興趣,你可以直接使用?`catch?{?...?}`?(甚至是作為唯一的catch子句) 而不是前面幾個catch子句呈野。 為了捕捉所有的錯誤情況低矮,你至少要有子句?`catch?{?...?}`?或?`catch?(bytes?memory?lowLevelData)?{?...?}`. 在?`returns`?和?`catch`?子句中聲明的變量只在后面的塊的范圍內(nèi)有效。 本文由[mdnice](https://mdnice.com/?platform=6)多平臺發(fā)布
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末被冒,一起剝皮案震驚了整個濱河市军掂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昨悼,老刑警劉巖蝗锥,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異率触,居然都是意外死亡终议,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門葱蝗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穴张,“玉大人,你說我怎么就攤上這事两曼≡砀剩” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵悼凑,是天一觀的道長偿枕。 經(jīng)常有香客問我,道長佛析,這世上最難降的妖魔是什么益老? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮寸莫,結(jié)果婚禮上捺萌,老公的妹妹穿的比我還像新娘。我一直安慰自己膘茎,他們只是感情好桃纯,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著披坏,像睡著了一般态坦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棒拂,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天伞梯,我揣著相機與錄音玫氢,去河邊找鬼。 笑死谜诫,一個胖子當(dāng)著我的面吹牛漾峡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喻旷,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼生逸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了且预?” 一聲冷哼從身側(cè)響起槽袄,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锋谐,沒想到半個月后遍尺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡涮拗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年狮鸭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片多搀。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖灾部,靈堂內(nèi)的尸體忽然破棺而出康铭,到底是詐尸還是另有隱情,我是刑警寧澤赌髓,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布从藤,位于F島的核電站,受9級特大地震影響锁蠕,放射性物質(zhì)發(fā)生泄漏夷野。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一荣倾、第九天 我趴在偏房一處隱蔽的房頂上張望悯搔。 院中可真熱鬧,春花似錦舌仍、人聲如沸妒貌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灌曙。三九已至,卻和暖如春节芥,著一層夾襖步出監(jiān)牢的瞬間在刺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚣驼,地道東北人魄幕。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像隙姿,于是被迫代替她去往敵國和親梅垄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容

  • solidity基礎(chǔ)-1 ReadMe測試環(huán)境:系統(tǒng)win10x64,solidity版本:0.4.18聲明:該筆...
    Lnhj閱讀 531評論 0 1
  • Solidity Solidity是編寫智能合約的語言输玷,運行在ethereum虛擬機上队丝。語法類似于JS,它擁有異常...
    暴走的K哥哥閱讀 4,897評論 2 2
  • ??Solidity是傳說中編寫智能合約的腳本語言欲鹏,運行在EVM中机久;用以解決區(qū)塊鏈中的任務(wù)執(zhí)行。一個目前看起來還非...
    楊強AT南京閱讀 5,536評論 0 1
  • 上一篇:智能合約編程語言 - solidity快速入門(上) solidity區(qū)塊及交易屬性 在介紹區(qū)塊及交易屬性...
    端碗吹水閱讀 1,957評論 0 3
  • solidity的自定義結(jié)構(gòu)體深入詳解結(jié)構(gòu)體赔嚎,solidity中的自定義類型膘盖,我們可以使用關(guān)鍵字struct來進(jìn)行...
    Lnhj閱讀 1,110評論 0 0