Solidity 快速指南



Solidity - 概覽

Solidity 是一種面向合約的高階程式語言,用於實現智慧合約。Solidity 受到 C++、Python 和 JavaScript 的很大影響,並且被設計為針對以太坊虛擬機器 (EVM)。

Solidity 是靜態型別的,支援繼承、庫和複雜的使用者定義型別程式語言。

您可以使用 Solidity 建立用於投票、眾籌、盲拍和多籤錢包等用途的合約。

什麼是以太坊?

以太坊是一個去中心化的,即區塊鏈平臺,它執行智慧合約,即按程式設計方式執行的應用程式,沒有任何停機、審查、欺詐或第三方干預的可能性。

以太坊虛擬機器 (EVM)

以太坊虛擬機器,也稱為 EVM,是以太坊中智慧合約的執行時環境。以太坊虛擬機器專注於提供安全性並透過世界各地的計算機執行不受信任的程式碼。

EVM 專注於防止拒絕服務攻擊,並確保程式無法訪問彼此的狀態,從而確保通訊可以在沒有任何潛在干擾的情況下建立。

以太坊虛擬機器旨在作為基於以太坊的智慧合約的執行時環境。

什麼是智慧合約?

智慧合約是一種計算機協議,旨在以數字方式促進、驗證或執行合同的協商或執行。智慧合約允許在沒有第三方的情況下執行可信交易。這些交易是可跟蹤且不可逆的。

智慧合約的概念最早由尼克·薩博 (Nick Szabo) 於 1994 年提出。薩博是一位法律學者和密碼學家,以奠定數字貨幣的基礎而聞名。

如果您現在不理解智慧合約,沒關係,我們稍後會詳細介紹。

Solidity - 環境設定

本章說明如何在 CentOS 機器上設定 Solidity 編譯器。如果您沒有 Linux 機器,則可以使用我們的線上編譯器來編譯小型合約並快速學習 Solidity。

方法 1 - npm / Node.js

這是在您的 CentoS 機器上安裝 Solidity 編譯器的最快方法。我們有以下步驟來安裝 Solidity 編譯器:

安裝 Node.js

首先確保您的 CentOS 機器上已安裝 node.js。如果尚未安裝,請使用以下命令安裝:

# First install epel-release
$sudo yum install epel-release

# Now install nodejs
$sudo yum install nodejs

# Next install npm (Nodejs Package Manager )
$sudo yum install npm

# Finally verify installation
$npm --version

如果一切安裝成功,您將看到類似以下的輸出:

3.10.10

安裝 solc

安裝 Node.js 包管理器後,您可以繼續如下安裝 Solidity 編譯器:

$sudonpm install -g solc

上述命令將安裝 solcjs 程式,並使其在整個系統中全域性可用。現在,您可以透過發出以下命令來測試您的 Solidity 編譯器:

$solcjs-version

如果一切順利,則將列印如下內容:

0.5.2+commit.1df8f40c.Emscripten.clang

現在您可以使用 solcjs 了,它比標準 Solidity 編譯器功能更少,但它將為您提供一個良好的起點。

方法 2 - Docker 映象

您可以拉取一個 Docker 映象並開始使用它來開始 Solidity 程式設計。以下是簡單的步驟。以下是拉取 Solidity Docker 映象的命令。

$docker pull ethereum/solc:stable

下載 Docker 映象後,我們可以使用以下命令驗證它。

$docker run ethereum/solc:stable-version

這將列印如下內容:

$ docker run ethereum/solc:stable -version

solc, the solidity compiler commandlineinterfaceVersion: 0.5.2+commit.1df8f40c.Linux.g++

方法 3:二進位制包安裝

如果您願意在 Linux 機器上安裝功能齊全的編譯器,請檢視官方網站安裝 Solidity 編譯器。

Solidity - 基本語法

Solidity 原始檔可以包含任意數量的合約定義、匯入指令和 pragma 指令。

讓我們從一個簡單的 Solidity 原始檔開始。以下是一個 Solidity 檔案的示例:

pragma solidity >=0.4.0 <0.6.0;
contract SimpleStorage {
   uint storedData;
   function set(uint x) public {
      storedData = x;
   }
   function get() public view returns (uint) {
      return storedData;
   }
}

Pragma

第一行是一個 pragma 指令,它表示原始碼是為 Solidity 版本 0.4.0 或任何不破壞功能的更新版本(不包括版本 0.6.0)編寫的。

pragma 指令始終是特定於原始檔的,如果您匯入另一個檔案,則該檔案中的 pragma 不會自動應用於匯入檔案。

因此,一個檔案在版本 0.4.0 之前無法編譯,並且在版本 0.5.0 及更高版本的編譯器上也無法工作的 pragma 將按如下方式編寫:

pragma solidity ^0.4.0;

這裡第二個條件是使用 ^ 新增的。

合約

Solidity 合約是程式碼(其函式)和資料(其狀態)的集合,駐留在以太坊區塊鏈上的特定地址。

uint storedData 行聲明瞭一個名為 storedData 的狀態變數,其型別為 uint,函式 set 和 get 可用於修改或檢索變數的值。

匯入檔案

儘管上面的示例沒有匯入語句,但 Solidity 支援與 JavaScript 中提供的非常相似的匯入語句。

以下語句從“檔名”匯入所有全域性符號。

import "filename";

以下示例建立一個新的全域性符號 symbolName,其成員是“檔名”中的所有全域性符號。

import * as symbolName from "filename";

要從與當前檔案相同的目錄匯入檔案 x,請使用 import "./x" as x;。如果您改為使用 import "x" as x;,則可能會在全域性“包含目錄”中引用不同的檔案。

保留關鍵字

以下是 Solidity 中的保留關鍵字:

abstract after alias apply
auto case catch copyof
default define final immutable
implements in inline let
macro match mutable null
of override partial promise
reference relocatable sealed sizeof
static supports switch try
typedef typeof unchecked

Solidity - 第一個應用程式

我們正在使用 Remix IDE 來編譯和執行我們的 Solidity 程式碼庫。

步驟 1 - 將給定的程式碼複製到 Remix IDE 程式碼部分。

示例

pragma solidity ^0.5.0;
contract SolidityTest {
   constructor() public{
   }
   function getResult() public view returns(uint){
      uint a = 1;
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

步驟 2 - 在“編譯”選項卡下,單擊“開始編譯”按鈕。

步驟 3 - 在“執行”選項卡下,單擊“部署”按鈕。

步驟 4 - 在“執行”選項卡下,在下拉列表中選擇“SolidityTest at 0x...”

步驟 5 - 單擊“getResult”按鈕以顯示結果。

輸出

0: uint256: 3

Solidity - 註釋

Solidity 支援 C 樣式和 C++ 樣式的註釋,因此:

  • // 和行尾之間的任何文字都被視為註釋,並且會被 Solidity 編譯器忽略。

  • /* 和 */ 字元之間的任何文字都被視為註釋。這可能跨越多行。

示例

以下示例顯示瞭如何在 Solidity 中使用註釋。

function getResult() public view returns(uint){
   // This is a comment. It is similar to comments in C++

   /*
      * This is a multi-line comment in solidity
      * It is very similar to comments in C Programming
   */
   uint a = 1;
   uint b = 2;
   uint result = a + b;
   return result;
}

Solidity - 資料型別

在任何語言中編寫程式時,您都需要使用各種變數來儲存各種資訊。變數只不過是保留的記憶體位置,用於儲存值。這意味著當您建立變數時,您會在記憶體中保留一些空間。

您可能希望儲存各種資料型別的資訊,例如字元、寬字元、整數、浮點數、雙精度浮點數、布林值等。根據變數的資料型別,作業系統分配記憶體並決定可以在保留的記憶體中儲存什麼。

值型別

Solidity 為程式設計師提供了豐富的內建和使用者定義資料型別。下表列出了七種基本 C++ 資料型別:

型別 關鍵字
布林值 bool true/false
整數 int/uint 不同大小的有符號和無符號整數。
整數 int8 到 int256 從 8 位到 256 位的有符號 int。int256 與 int 相同。
整數 uint8 到 uint256 從 8 位到 256 位的無符號 int。uint256 與 uint 相同。
定點數 fixed/unfixed 不同大小的有符號和無符號定點數。
定點數 fixed/unfixed 不同大小的有符號和無符號定點數。
定點數 fixedMxN 有符號定點數,其中 M 表示型別佔用的位數,N 表示小數位數。M 應為 8 的倍數,範圍從 8 到 256。N 可以是 0 到 80。fixed 與 fixed128x18 相同。
定點數 ufixedMxN

無符號定點數,其中 M 表示型別佔用的位數,N 表示小數位數。M 必須是 8 的倍數,範圍從 8 到 256。N 的範圍可以是 0 到 80。ufixed 與 ufixed128x18 相同。

注意:您還可以將有符號和無符號定點數表示為 fixedMxN/ufixedMxN,其中 M 表示型別佔用的位數,N 表示小數位數。M 必須是 8 的倍數,範圍從 8 到 256。N 的範圍可以是 0 到 80。

地址

address 儲存表示以太坊地址大小的 20 位元組值。可以使用 .balance 方法獲取地址的餘額,並可以使用 .transfer 方法將餘額轉賬到另一個地址。

address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

Solidity - 變數

Solidity 支援三種類型的變數。

  • 狀態變數 - 值永久儲存在合約儲存中的變數。

  • 區域性變數 - 值在函式執行期間存在的變數。

  • 全域性變數 - 存在於全域性名稱空間中用於獲取有關區塊鏈資訊的特殊變數。

Solidity 是一種靜態型別語言,這意味著在宣告期間需要指定狀態或區域性變數的型別。每個宣告的變數都根據其型別具有預設值。沒有“未定義”或“空”的概念。

狀態變數

值永久儲存在合約儲存中的變數。

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData;      // State variable
   constructor() public {
      storedData = 10;   // Using State variable
   }
}

區域性變數

僅在定義它的函式內可用的變數。函式引數始終是該函式的區域性變數。

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // State variable
   constructor() public {
      storedData = 10;   
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return result; //access the local variable
   }
}

示例

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // State variable
   constructor() public {
      storedData = 10;   
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return storedData; //access the state variable
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: uint256: 10

全域性變數

這些是存在於全域性工作區中的特殊變數,提供有關區塊鏈和交易屬性的資訊。

名稱 返回值
blockhash(uint blockNumber) returns (bytes32) 給定區塊的雜湊值 - 僅適用於最近的 256 個區塊(不包括當前區塊)
block.coinbase (address payable) 當前區塊礦工的地址
block.difficulty (uint) 當前區塊難度
block.gaslimit (uint) 當前區塊 gas 限制
block.number (uint) 當前區塊編號
block.timestamp (uint) 自 Unix 紀元以來的當前區塊時間戳(以秒為單位)
gasleft() returns (uint256) 剩餘 gas
msg.data (bytes calldata) 完整的 calldata
msg.sender (address payable) 訊息傳送者(當前呼叫者)
msg.sig (bytes4) calldata 的前四個位元組(函式識別符號)
msg.value (uint) 與訊息一起傳送的 Wei 數
now (uint) 當前區塊時間戳
tx.gasprice (uint) 交易的 gas 價格
tx.origin (address payable) 交易傳送者

Solidity 變數命名

在 Solidity 中命名變數時,請記住以下規則。

  • 您不應使用任何 Solidity 保留關鍵字作為變數名。這些關鍵字將在下一節中提到。例如,break 或 boolean 變數名無效。

  • Solidity 變數名不能以數字 (0-9) 開頭。它們必須以字母或下劃線字元開頭。例如,123test 是無效的變數名,但 _123test 是有效的變數名。

  • Solidity 變數名區分大小寫。例如,Name 和 name 是兩個不同的變數。

Solidity - 變數作用域

區域性變數的作用域僅限於其定義的函式,但狀態變數可以有三種類型的作用域。

  • 公共 - 公共狀態變數可以在內部以及透過訊息訪問。對於公共狀態變數,會生成一個自動的 getter 函式。

  • 內部 - 內部狀態變數只能從當前合約或從中派生的合約內部訪問,無需使用 this。

  • 私有 - 私有狀態變數只能從定義它們的當前合約內部訪問,不能在從中派生的合約中訪問。

示例

pragma solidity ^0.5.0;
contract C {
   uint public data = 30;
   uint internal iData= 10;
   
   function x() public returns (uint) {
      data = 3; // internal access
      return data;
   }
}
contract Caller {
   C c = new C();
   function f() public view returns (uint) {
      return c.data(); //external access
   }
}
contract D is C {
   function y() public returns (uint) {
      iData = 3; // internal access
      return iData;
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return storedData; //access the state variable
   }
}

Solidity - 運算子

什麼是運算子?

讓我們來看一個簡單的表示式4 + 5 等於 9。這裡 4 和 5 稱為運算元,'+' 稱為運算子。Solidity 支援以下型別的運算子。

  • 算術運算子
  • 比較運算子
  • 邏輯(或關係)運算子
  • 賦值運算子
  • 條件(或三元)運算子

讓我們逐一瞭解所有運算子。

算術運算子

Solidity 支援以下算術運算子 -

假設變數 A 持有 10,變數 B 持有 20,則 -

顯示示例

序號 運算子和描述
1

+ (加法)

將兩個運算元相加

例如:A + B 將得到 30

2

- (減法)

從第一個運算元中減去第二個運算元

例如:A - B 將得到 -10

3

* (乘法)

將兩個運算元相乘

例如:A * B 將得到 200

4

/ (除法)

將分子除以分母

例如:B / A 將得到 2

5

% (模)

輸出整數除法的餘數

例如:B % A 將得到 0

6

++ (自增)

將整數值增加 1

例如:A++ 將得到 11

7

-- (自減)

將整數值減少 1

例如:A-- 將得到 9

比較運算子

Solidity 支援以下比較運算子 -

假設變數 A 持有 10,變數 B 持有 20,則 -

顯示示例

序號 運算子和描述
1

= = (等於)

檢查兩個運算元的值是否相等,如果相等,則條件變為真。

例如:(A == B) 為假。

2

!= (不等於)

檢查兩個運算元的值是否相等,如果值不相等,則條件變為真。

例如:(A != B) 為真。

3

> (大於)

檢查左側運算元的值是否大於右側運算元的值,如果大於,則條件變為真。

例如:(A > B) 為假。

4

< (小於)

檢查左側運算元的值是否小於右側運算元的值,如果小於,則條件變為真。

例如:(A < B) 為真。

5

>= (大於或等於)

檢查左側運算元的值是否大於或等於右側運算元的值,如果大於或等於,則條件變為真。

例如:(A >= B) 為假。

6

<= (小於或等於)

檢查左側運算元的值是否小於或等於右側運算元的值,如果小於或等於,則條件變為真。

例如:(A <= B) 為真。

邏輯運算子

Solidity 支援以下邏輯運算子 -

假設變數 A 持有 10,變數 B 持有 20,則 -

顯示示例

序號 運算子和描述
1

&& (邏輯與)

如果兩個運算元均不為零,則條件變為真。

例如:(A && B) 為真。

2

|| (邏輯或)

如果兩個運算元中的任何一個不為零,則條件變為真。

例如:(A || B) 為真。

3

! (邏輯非)

反轉其運算元的邏輯狀態。如果條件為真,則邏輯非運算子將使其變為假。

例如:! (A && B) 為假。

位運算子

Solidity 支援以下位運算子 -

假設變數 A 持有 2,變數 B 持有 3,則 -

顯示示例

序號 運算子和描述
1

& (按位與)

它對每個整數引數的每一位執行布林 AND 運算。

例如:(A & B) 為 2。

2

| (按位或)

它對每個整數引數的每一位執行布林 OR 運算。

例如:(A | B) 為 3。

3

^ (按位異或)

它對每個整數引數的每一位執行布林異或運算。異或意味著運算元一或運算元二為真,但兩者不能同時為真。

例如:(A ^ B) 為 1。

4

~ (按位非)

它是一個一元運算子,透過反轉運算元中的所有位來操作。

例如:(~B) 為 -4。

5

<< (左移)

它將第一個運算元中的所有位向左移動第二個運算元中指定的位數。新位用零填充。將值左移一位相當於將其乘以 2,左移兩位相當於將其乘以 4,依此類推。

例如:(A << 1) 為 4。

6

>> (右移)

二進位制右移運算子。左側運算元的值向右移動右側運算元指定的位數。

例如:(A >> 1) 為 1。

7

>>> (帶零的右移)

此運算子與 >> 運算子類似,只是左側移入的位始終為零。

例如:(A >>> 1) 為 1。

賦值運算子

Solidity 支援以下賦值運算子 -

顯示示例

序號 運算子和描述
1

= (簡單賦值)

將右側運算元的值賦給左側運算元

例如:C = A + B 將 A + B 的值賦給 C

2

+= (加法賦值)

它將右側運算元加到左側運算元,並將結果賦給左側運算元。

例如:C += A 等價於 C = C + A

3

−= (減法賦值)

它從左側運算元中減去右側運算元,並將結果賦給左側運算元。

例如:C -= A 等價於 C = C - A

4

*= (乘法賦值)

它將右側運算元乘以左側運算元,並將結果賦給左側運算元。

例如:C *= A 等價於 C = C * A

5

/= (除法賦值)

它將左側運算元除以右側運算元,並將結果賦給左側運算元。

例如:C /= A 等價於 C = C / A

6

%= (模賦值)

它使用兩個運算元進行取模運算,並將結果賦給左側運算元。

例如:C %= A 等價於 C = C % A

注意 - 位運算子也適用相同的邏輯,因此它們將變為 <<=、>>=、>>=、&=、|= 和 ^=。

條件運算子 (? :)

條件運算子首先評估表示式的真假值,然後根據評估結果執行兩個給定語句之一。

顯示示例

序號 運算子和描述
1

? : (條件)

如果條件為真?則值為 X:否則為 Y

Solidity - 迴圈

在編寫合約時,您可能會遇到需要反覆執行某個操作的情況。在這種情況下,您需要編寫迴圈語句以減少程式碼行數。

Solidity 支援所有必要的迴圈來減輕程式設計壓力。

序號 迴圈和描述
1

While 迴圈

Solidity 中最基本的迴圈是 while 迴圈,將在本章中討論。

2

do...while 迴圈

do...while 迴圈與 while 迴圈類似,只是條件檢查發生在迴圈的末尾。

3

For 迴圈

for 迴圈是迴圈最緊湊的形式。它包括以下三個重要部分。

4

迴圈控制

Solidity 提供了完全的控制來處理迴圈和 switch 語句。

Solidity - 條件語句

在編寫程式時,可能會出現需要從給定的一組路徑中選擇一條的情況。在這種情況下,您需要使用條件語句,使您的程式能夠做出正確的決策並執行正確的操作。

Solidity 支援條件語句,用於根據不同的條件執行不同的操作。這裡我們將解釋if..else 語句。

if-else 流程圖

以下流程圖顯示了 if-else 語句的工作原理。

Decision Making

Solidity 支援以下形式的if..else 語句:

序號 語句及描述
1

if 語句

if 語句是基本的控制語句,允許 Solidity 進行決策並有條件地執行語句。

2

if...else 語句

“if...else”語句是下一種控制語句形式,允許 Solidity 以更受控的方式執行語句。

3

if...else if... 語句。

if...else if... 語句是 if...else 的高階形式,允許 Solidity 從多個條件中做出正確的決策。

Solidity - 字串

Solidity 使用雙引號(")和單引號(')都支援字串字面量。它提供字串作為資料型別來宣告字串型別的變數。

pragma solidity ^0.5.0;

contract SolidityTest {
   string data = "test";
}

在上面的例子中,“test”是一個字串字面量,data 是一個字串變數。更推薦的方式是使用位元組型別而不是字串,因為字串操作比位元組操作需要更多的 gas。Solidity 提供了位元組到字串和反向的內建轉換。在 Solidity 中,我們可以輕鬆地將字串字面量賦值給 byte32 型別的變數。Solidity 將其視為 byte32 字面量。

pragma solidity ^0.5.0;

contract SolidityTest {
   bytes32 data = "test";
}

跳脫字元

序號 字元及描述
1

\n

開始新行。

2

\\

反斜槓

3

\'

單引號

4

\"

雙引號

5

\b

退格

6

\f

換頁

7

\r

回車

8

\t

製表符

9

\v

垂直製表符

10

\xNN

表示十六進位制值並插入相應的位元組。

11

\uNNNN

表示 Unicode 值並插入 UTF-8 序列。

位元組到字串轉換

可以使用 string() 建構函式將位元組轉換為字串。

bytes memory bstr = new bytes(10);
string message = string(bstr);   

示例

嘗試以下程式碼以瞭解字串在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: string: 3

Solidity - 陣列

陣列是一種資料結構,它儲存相同型別元素的固定大小的順序集合。陣列用於儲存資料集合,但通常將其視為相同型別變數的集合更有用。

與其宣告單獨的變數,例如 number0、number1、... 和 number99,不如宣告一個數組變數,例如 numbers,並使用 numbers[0]、numbers[1] 和 ...、numbers[99] 來表示各個變數。陣列中的特定元素透過索引訪問。

在 Solidity 中,陣列可以是編譯時固定大小的,也可以是動態大小的。對於儲存陣列,它也可以具有不同型別的元素。對於記憶體陣列,元素型別不能是對映,如果要將其用作函式引數,則元素型別應為 ABI 型別。

所有陣列都由連續的記憶體位置組成。最低地址對應於第一個元素,最高地址對應於最後一個元素。

宣告陣列

要在 Solidity 中宣告固定大小的陣列,程式設計師需要指定元素的型別以及陣列所需的元素數量,如下所示:

type arrayName [ arraySize ];

這稱為一維陣列。arraySize 必須是大於零的整數常量,type 可以是任何有效的 Solidity 資料型別。例如,要宣告一個名為 balance 的 10 個元素的 uint 型別陣列,請使用以下語句:

uint balance[10];

要在 Solidity 中宣告動態大小的陣列,程式設計師需要指定元素的型別,如下所示:

type[] arrayName;

初始化陣列

您可以逐個初始化 Solidity 陣列元素,或者使用單個語句,如下所示:

uint balance[3] = [1, 2, 3];

花括號 [ ] 之間的值數量不能大於我們在方括號 [ ] 之間為陣列宣告的元素數量。以下是如何賦值陣列單個元素的示例:

如果省略陣列的大小,則會建立一個足夠容納初始化的陣列。因此,如果您編寫:

uint balance[] = [1, 2, 3];

您將建立與上一個示例中完全相同的陣列。

balance[2] = 5;

以上語句將陣列中第 3 個元素的值賦為 5。

建立動態記憶體陣列

動態記憶體陣列是使用 new 關鍵字建立的。

uint size = 3;
uint balance[] = new uint[](size);

訪問陣列元素

透過索引陣列名稱來訪問元素。這是透過在陣列名稱後面方括號內放置元素的索引來完成的。例如:

uint salary = balance[2];

以上語句將從陣列中獲取第 3 個元素並將值賦給 salary 變數。以下是一個示例,它將使用上述所有三個概念,即宣告、賦值和訪問陣列:

成員

  • length - length 返回陣列的大小。length 可用於透過設定它來更改動態陣列的大小。

  • push - push 允許在末尾將元素附加到動態儲存陣列。它返回陣列的新長度。

示例

嘗試以下程式碼以瞭解陣列在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract test {
   function testArray() public pure{
      uint len = 7; 
      
      //dynamic array
      uint[] memory a = new uint[](7);
      
      //bytes is same as byte[]
      bytes memory b = new bytes(len);
      
      assert(a.length == 7);
      assert(b.length == len);
      
      //access array variable
      a[6] = 8;
      
      //test array variable
      assert(a[6] == 8);
      
      //static array
      uint[3] memory c = [uint(1) , 2, 3];
      assert(c.length == 3);
   }
}

Solidity - 列舉

列舉將變數限制為僅具有幾個預定義值中的一個。此列舉列表中的值稱為列舉。

透過使用列舉,可以減少程式碼中的錯誤數量。

例如,如果我們考慮一個鮮榨果汁店的應用程式,則可以將杯子尺寸限制為小、中和大。這將確保不允許任何人訂購除小、中或大以外的任何尺寸。

示例

嘗試以下程式碼以瞭解列舉在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract test {
   enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
   FreshJuiceSize choice;
   FreshJuiceSize constant defaultChoice = FreshJuiceSize.MEDIUM;

   function setLarge() public {
      choice = FreshJuiceSize.LARGE;
   }
   function getChoice() public view returns (FreshJuiceSize) {
      return choice;
   }
   function getDefaultChoice() public pure returns (uint) {
      return uint(defaultChoice);
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

首先點選setLarge按鈕將值設定為 LARGE,然後點選getChoice獲取所選選項。

輸出

uint8: 2

點選getDefaultChoice按鈕獲取預設選項。

輸出

uint256: 1

Solidity - 結構體

結構型別用於表示記錄。假設您想跟蹤圖書館中的書籍。您可能希望跟蹤每本書的以下屬性:

  • 標題
  • 作者
  • 主題
  • 圖書 ID

定義結構

要定義結構,必須使用struct關鍵字。struct 關鍵字定義了一種新的資料型別,它具有多個成員。結構語句的格式如下:

struct struct_name { 
   type1 type_name_1;
   type2 type_name_2;
   type3 type_name_3;
}

示例

struct Book { 
   string title;
   string author;
   uint book_id;
}

訪問結構及其變數

要訪問結構的任何成員,我們使用成員訪問運算子 (.)。成員訪問運算子被編碼為結構變數名稱和我們希望訪問的結構成員之間的句點。您將使用 struct 來定義結構型別變數。以下示例顯示瞭如何在程式中使用結構。

示例

嘗試以下程式碼以瞭解結構在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract test {
   struct Book { 
      string title;
      string author;
      uint book_id;
   }
   Book book;

   function setBook() public {
      book = Book('Learn Java', 'TP', 1);
   }
   function getBookId() public view returns (uint) {
      return book.book_id;
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

首先點選setBook按鈕將值設定為 LARGE,然後點選getBookId獲取所選圖書 ID。

輸出

uint256: 1

Solidity - 對映

對映與陣列和結構一樣是一種引用型別。以下是宣告對映型別的語法。

mapping(_KeyType => _ValueType)

其中

  • _KeyType - 可以是任何內建型別以及位元組和字串。不允許使用引用型別或複雜物件。

  • _ValueType - 可以是任何型別。

注意事項

  • 對映只能具有storage型別,通常用於狀態變數。

  • 對映可以標記為 public。Solidity 會自動為其建立 getter。

示例

嘗試以下程式碼以瞭解對映型別在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

首先點選updateBalance按鈕將值設定為 10,然後檢視日誌,它將顯示解碼輸出為:

輸出

{
   "0": "uint256: 10"
}

Solidity - 型別轉換

Solidity 允許隱式和顯式轉換。Solidity 編譯器允許在兩種資料型別之間進行隱式轉換,前提是沒有可能的隱式轉換並且沒有資訊丟失。例如,uint8 可轉換為 uint16,但 int8 可轉換為 uint256,因為 int8 可以包含 uint256 中不允許的負值。

顯式轉換

我們可以使用建構函式語法將一種資料型別顯式轉換為另一種資料型別。

int8 y = -3;
uint x = uint(y);
//Now x = 0xfffff..fd == two complement representation of -3 in 256 bit format.

轉換為較小型別會損失高位。

uint32 a = 0x12345678;
uint16 b = uint16(a); // b = 0x5678

轉換為較高型別會在左側新增填充位。

uint16 a = 0x1234;
uint32 b = uint32(a); // b = 0x00001234 

轉換為較小位元組會損失高位資料。

bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b = 0x12

轉換為較大位元組會在右側新增填充位。

bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b = 0x12340000

固定大小位元組和 int 之間的轉換僅在兩者大小相同時才可能。

bytes2 a = 0x1234;
uint32 b = uint16(a); // b = 0x00001234
uint32 c = uint32(bytes4(a)); // c = 0x12340000
uint8 d = uint8(uint16(a)); // d = 0x34
uint8 e = uint8(bytes1(a)); // e = 0x12

如果不需要截斷,則可以將十六進位制數字賦值給任何整數型別。

uint8 a = 12; // no error
uint32 b = 1234; // no error
uint16 c = 0x123456; // error, as truncation required to 0x3456

Solidity - 以太坊單位

在 solidity 中,我們可以使用 wei、finney、szabo 或 ether 作為字面量的字尾來用於轉換各種基於 ether 的面額。最小的單位是 wei,1e12 表示 1 x 1012

assert(1 wei == 1);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
assert(2 ether == 2000 fenny);

時間單位

與貨幣類似,Solidity 也有時間單位,其中最小的單位是秒,我們可以使用 seconds、minutes、hours、days 和 weeks 作為字尾來表示時間。

assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 day == 24 hours);
assert(1 week == 7 days);

Solidity - 特殊變數

特殊變數是全域性可用的變數,並提供有關區塊鏈的資訊。以下是特殊變數的列表:

序號 特殊變數及描述
1

blockhash(uint blockNumber) returns (bytes32)

給定區塊的雜湊 - 僅適用於最近的 256 個區塊(不包括當前區塊)。

2

block.coinbase (address payable)

當前區塊礦工的地址。

3

block.difficulty (uint)

當前區塊難度。

4

block.gaslimit (uint)

當前區塊 gaslimit。

5

block.number (uint)

當前區塊編號。

6

block.timestamp

自 Unix 紀元以來的當前區塊時間戳(以秒為單位)。

7

gasleft() returns (uint256)

剩餘 gas。

8

msg.data (bytes calldata)

完整的 calldata。

9

msg.sender (address payable)

訊息的傳送者(當前呼叫)。

10

msg.sig (bytes4)

calldata 的前四個位元組(即函式識別符號)

11

msg.value (uint)

隨訊息傳送的 wei 數量。

12

now (uint)

當前區塊時間戳(block.timestamp 的別名)。

13

tx.gasprice (uint)

交易的 gas 價格。

14

tx.origin (address payable)

交易的傳送者(完整的呼叫鏈)。

示例

嘗試以下程式碼以檢視 msg 的用法,這是一個特殊變數,用於在 Solidity 中獲取傳送者地址。

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

首先點選updateBalance按鈕將值設定為 10,然後檢視日誌,它將顯示解碼輸出為:

輸出

{
   "0": "uint256: 10"
}

Solidity - 樣式指南

樣式指南有助於保持程式碼佈局一致並使程式碼更易於閱讀。以下是使用 Solidity 編寫合約時應遵循的最佳實踐。

程式碼佈局

  • 縮排 - 使用 4 個空格代替製表符來維護縮排級別。避免混合空格和製表符。

  • 兩空行規則 - 在兩個合約定義之間使用 2 個空行。

pragma solidity ^0.5.0;

contract LedgerBalance {
   //...
}
contract Updater {
   //...
}
  • 一個空行規則 - 在兩個函式之間使用 1 個空行。如果只有宣告,則無需空行。

pragma solidity ^0.5.0;

contract A {
   function balance() public pure;
   function account() public pure;
}
contract B is A {
   function balance() public pure {
      // ...
   }
   function account() public pure {
      // ...
   }
}
  • 最大行長 - 單行不應超過 79 個字元,以便讀者可以輕鬆地解析程式碼。

  • 換行規則 - 第一個引數應在新行中,不帶開括號。每個引數使用單個縮排。結束元素 ); 應是最後一個。

function_with_a_long_name(
   longArgument1,
   longArgument2,
   longArgument3
);
variable = function_with_a_long_name(
   longArgument1,
   longArgument2,
   longArgument3
);
event multipleArguments(
   address sender,
   address recipient,
   uint256 publicKey,
   uint256 amount,
   bytes32[] options
);
MultipleArguments(
   sender,
   recipient,
   publicKey,
   amount,
   options
);
  • 原始碼編碼 - 最好使用 UTF-8 或 ASCII 編碼。

  • 匯入 - 匯入語句應放在檔案的頂部,緊跟在 pragma 宣告之後。

  • 函式順序 - 函式應根據其可見性進行分組。

pragma solidity ^0.5.0;

contract A {
   constructor() public {
      // ...
   }
   function() external {
      // ...
   }

   // External functions
   // ...

   // External view functions
   // ...

   // External pure functions 
   // ...

   // Public functions
   // ...

   // Internal functions
   // ...

   // Private functions
   // ...
}
  • 避免多餘的空格 - 避免在括號、括號或花括號內立即使用空格。

  • 控制結構 - 花括號應與宣告在同一行開啟。在自己的行上關閉,保持相同的縮排。在開花括號前使用空格。

pragma solidity ^0.5.0;

contract Coin {
   struct Bank {
      address owner;
      uint balance;
   }
}
if (x < 3) {
   x += 1;
} else if (x > 7) {
   x -= 1;
} else {
   x = 5;
}
if (x < 3)
   x += 1;
else
   x -= 1;
  • 函式宣告 - 對花括號使用上述規則。始終新增可見性標籤。可見性標籤應在任何自定義修飾符之前。

function kill() public onlyowner {
   selfdestruct(owner);
}
  • 對映 - 宣告對映變數時避免使用空格。

mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
  • 變數宣告 - 宣告陣列變數時避免使用空格。

uint[] x;  // not unit [] x;
  • 字串宣告 - 使用雙引號而不是單引號來宣告字串。

str = "foo";
str = "Hamlet says, 'To be or not to be...'";

佈局順序

元素應按以下順序佈局。

  • Pragma 語句

  • 匯入語句

  • 介面

  • 合約

在介面、庫或合約中,順序應為:

  • 型別宣告

  • 狀態變數

  • 事件

  • 函式

命名約定

  • 合約和庫應使用 CapWords 樣式命名。例如,SmartContract、Owner 等。

  • 合約和庫名稱應與其檔名匹配。

  • 如果一個檔案中有多個合約/庫,請使用核心合約/庫的名稱。

Owned.sol

pragma solidity ^0.5.0;

// Owned.sol
contract Owned {
   address public owner;
   constructor() public {
      owner = msg.sender;
   }
   modifier onlyOwner {
      //....
   }
   function transferOwnership(address newOwner) public onlyOwner {
      //...
   }
}

Congress.sol

pragma solidity ^0.5.0;

// Congress.sol
import "./Owned.sol";

contract Congress is Owned, TokenRecipient {
   //...
}
  • 結構名稱

    - 使用 CapWords 樣式,例如 SmartCoin。

  • 事件名稱

    - 使用 CapWords 樣式,例如 Deposit、AfterTransfer。

  • 函式名稱

    − 使用混合大小寫風格,例如 initiateSupply。

  • 區域性變數和狀態變數

    − 使用混合大小寫風格,例如 creatorAddress、supply。

  • 常量

    − 使用全大寫字母,並使用下劃線分隔單詞,例如 MAX_BLOCKS。

  • 修飾符名稱

    − 使用混合大小寫風格,例如 onlyAfter。

  • 列舉名稱

    − 使用首字母大寫風格,例如 TokenGroup。

Solidity - 函式

函式是一組可重用的程式碼,可以在程式的任何地方呼叫。這避免了重複編寫相同程式碼的需要。它幫助程式設計師編寫模組化程式碼。函式允許程式設計師將一個大型程式分解成許多小的、易於管理的函式。

與任何其他高階程式語言一樣,Solidity 也支援編寫使用函式的模組化程式碼所需的所有功能。本節說明如何在 Solidity 中編寫自己的函式。

函式定義

在使用函式之前,我們需要定義它。在 Solidity 中定義函式最常見的方法是使用 **function** 關鍵字,後跟唯一的函式名稱、引數列表(可能為空)以及用大括號括起來的一組語句。

語法

基本語法如下所示。

function function-name(parameter-list) scope returns() {
   //statements
}

示例

嘗試以下示例。它定義了一個名為 getResult 的函式,該函式不接受任何引數 -

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

呼叫函式

要在合約中的其他地方呼叫函式,只需編寫該函式的名稱,如下面的程式碼所示。

嘗試以下程式碼以瞭解字串在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);//access local variable
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: string: 3

函式引數

到目前為止,我們已經看到了沒有引數的函式。但是,在呼叫函式時,可以使用不同的引數。這些傳遞的引數可以在函式內部捕獲,並且可以對這些引數進行任何操作。函式可以接收多個引數,用逗號分隔。

示例

嘗試以下示例。我們在這裡使用了 **uint2str** 函式。它接受一個引數。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);//access local variable
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: string: 3

return 語句

Solidity 函式可以有一個可選的 **return** 語句。如果要從函式返回一個值,則需要此語句。此語句應為函式中的最後一條語句。

如上例所示,我們使用 uint2str 函式返回一個字串。

在 Solidity 中,函式也可以返回多個值。請參見下面的示例 -

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // local variable
      uint b = 2;
      product = a * b;
      sum = a + b;
  
      //alternative return statement to return 
      //multiple values
      //return(a*b, a+b);
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: uint256: product 2
1: uint256: sum 3

Solidity - 函式修飾符

函式修飾符用於修改函式的行為。例如,向函式新增先決條件。

首先,我們建立一個帶或不帶引數的修飾符。

contract Owner {
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}

函式體插入修飾符定義中出現特殊符號“_;”的地方。因此,如果在呼叫此函式時滿足修飾符的條件,則執行該函式,否則丟擲異常。

請參見下面的示例 -

pragma solidity ^0.5.0;

contract Owner {
   address owner;
   constructor() public {
      owner = msg.sender;
   }
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}
contract Register is Owner {
   mapping (address => bool) registeredAddresses;
   uint price;
   constructor(uint initialPrice) public { price = initialPrice; }
   
   function register() public payable costs(price) {
      registeredAddresses[msg.sender] = true;
   }
   function changePrice(uint _price) public onlyOwner {
      price = _price;
   }
}

Solidity - View 函式

檢視函式確保它們不會修改狀態。函式可以宣告為 **view**。如果函式中存在以下語句,則認為它們正在修改狀態,編譯器將在這種情況下發出警告。

  • 修改狀態變數。

  • 發出事件。

  • 建立其他合約。

  • 使用 selfdestruct。

  • 透過呼叫傳送以太坊。

  • 呼叫任何未標記為 view 或 pure 的函式。

  • 使用低階呼叫。

  • 使用包含某些操作碼的內聯彙編。

Getter 方法預設是檢視函式。

請參見下面使用檢視函式的示例。

示例

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // local variable
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: uint256: product 2
1: uint256: sum 3

Solidity - Pure 函式

純函式確保它們不讀取或修改狀態。函式可以宣告為 **pure**。如果函式中存在以下語句,則認為它們正在讀取狀態,編譯器將在這種情況下發出警告。

  • 讀取狀態變數。

  • 訪問 address(this).balance 或 <address>.balance。

  • 訪問塊、tx、msg 的任何特殊變數(可以讀取 msg.sig 和 msg.data)。

  • 呼叫任何未標記為 pure 的函式。

  • 使用包含某些操作碼的內聯彙編。

純函式可以使用 revert() 和 require() 函式在發生錯誤時回滾潛在的狀態更改。

請參見下面使用檢視函式的示例。

示例

pragma solidity ^0.5.0;

contract Test {
   function getResult() public pure returns(uint product, uint sum){
      uint a = 1; 
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: uint256: product 2
1: uint256: sum 3

Solidity - 回退函式

回退函式是合約中可用的特殊函式。它具有以下功能 -

  • 當在合約上呼叫不存在的函式時呼叫它。

  • 它需要標記為 external。

  • 它沒有名稱。

  • 它沒有引數

  • 它不能返回任何內容。

  • 每個合約可以定義一個。

  • 如果未標記為 payable,則如果合約在沒有資料的情況下接收普通以太坊,它將丟擲異常。

以下示例顯示了每個合約的回退函式的概念。

示例

pragma solidity ^0.5.0;

contract Test {
   uint public x ;
   function() external { x = 1; }    
}
contract Sink {
   function() external payable { }
}
contract Caller {
   function callTest(Test test) public returns (bool) {
      (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
      require(success);
      // test.x is now 1

      address payable testPayable = address(uint160(address(test)));

      // Sending ether to Test contract,
      // the transfer will fail, i.e. this returns false here.
      return (testPayable.send(2 ether));
   }
   function callSink(Sink sink) public returns (bool) {
      address payable sinkPayable = address(sink);
      return (sinkPayable.send(2 ether));
   }
}

Solidity - 函式過載

您可以在相同的範圍內為同一個函式名稱提供多個定義。函式的定義必須在引數列表中引數的型別和/或數量上有所不同。您不能過載僅返回值型別不同的函式宣告。

以下示例顯示了 Solidity 中函式過載的概念。

示例

pragma solidity ^0.5.0;

contract Test {
   function getSum(uint a, uint b) public pure returns(uint){      
      return a + b;
   }
   function getSum(uint a, uint b, uint c) public pure returns(uint){      
      return a + b + c;
   }
   function callSumWithTwoArguments() public pure returns(uint){
      return getSum(1,2);
   }
   function callSumWithThreeArguments() public pure returns(uint){
      return getSum(1,2,3);
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

先點選 callSumWithTwoArguments 按鈕,然後點選 callSumWithThreeArguments 按鈕檢視結果。

輸出

0: uint256: 3
0: uint256: 6

Solidity - 數學函式

Solidity 還提供內建的數學函式。以下是經常使用的函式 -

  • **addmod(uint x, uint y, uint k) returns (uint)** - 計算 (x + y) % k,其中加法以任意精度執行,並且不會在 2256 處環繞。

  • **mulmod(uint x, uint y, uint k) returns (uint)** - 計算 (x * y) % k,其中加法以任意精度執行,並且不會在 2256 處環繞。

以下示例顯示了在 Solidity 中使用數學函式。

示例

pragma solidity ^0.5.0;

contract Test {   
   function callAddMod() public pure returns(uint){
      return addmod(4, 5, 3);
   }
   function callMulMod() public pure returns(uint){
      return mulmod(4, 5, 3);
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

先點選 callAddMod 按鈕,然後點選 callMulMod 按鈕檢視結果。

輸出

0: uint256: 0
0: uint256: 2

Solidity - 密碼學函式

Solidity 還提供內建的密碼學函式。以下是重要的函式 -

  • **keccak256(bytes memory) returns (bytes32)** - 計算輸入的 Keccak-256 雜湊值。

  • **ripemd160(bytes memory) returns (bytes20)** - 計算輸入的 RIPEMD-160 雜湊值。

  • **sha256(bytes memory) returns (bytes32)** - 計算輸入的 SHA-256 雜湊值。

  • **ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)** - 從橢圓曲線簽名中恢復與公鑰關聯的地址,或在出錯時返回零。函式引數對應於簽名的 ECDSA 值:r - 簽名的前 32 個位元組;s:簽名的第二個 32 個位元組;v:簽名的最後一個 1 個位元組。此方法返回一個地址。

以下示例顯示了在 Solidity 中使用密碼學函式。

示例

pragma solidity ^0.5.0;

contract Test {   
   function callKeccak256() public pure returns(bytes32 result){
      return keccak256("ABC");
   }  
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: bytes32: result 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8

Solidity - 提款模式

提款模式確保不會進行直接轉賬呼叫,這會帶來安全威脅。以下合約顯示了使用轉賬呼叫傳送以太坊的不安全用法。

pragma solidity ^0.5.0;

contract Test {
   address payable public richest;
   uint public mostSent;

   constructor() public payable {
      richest = msg.sender;
      mostSent = msg.value;
   }
   function becomeRichest() public payable returns (bool) {
      if (msg.value > mostSent) {
         // Insecure practice
         richest.transfer(msg.value);
         richest = msg.sender;
         mostSent = msg.value;
         return true;
      } else {
         return false;
      }
   }
}

以上合約可以透過導致最富有者成為回退函式失敗的合約來使其處於不可用狀態。當回退函式失敗時,becomeRichest() 函式也會失敗,合約將永遠卡住。為了緩解這個問題,我們可以使用提款模式。

在提款模式中,我們將在每次轉賬前重置掛起的金額。它將確保只有呼叫者合約失敗。

pragma solidity ^0.5.0;

contract Test {
   address public richest;
   uint public mostSent;

   mapping (address => uint) pendingWithdrawals;

   constructor() public payable {
      richest = msg.sender;
      mostSent = msg.value;
   }
   function becomeRichest() public payable returns (bool) {
      if (msg.value > mostSent) {
         pendingWithdrawals[richest] += msg.value;
         richest = msg.sender;
         mostSent = msg.value;
         return true;
      } else {
         return false;
      }
   }
   function withdraw() public {
      uint amount = pendingWithdrawals[msg.sender];
      pendingWithdrawals[msg.sender] = 0;
      msg.sender.transfer(amount);
   }
}

Solidity - 訪問限制

限制對合約的訪問是一種常見做法。預設情況下,合約狀態為只讀,除非將其指定為 public。

我們可以使用修飾符限制誰可以修改合約的狀態或呼叫合約的函式。我們將建立和使用多個修飾符,如下所述 -

  • **onlyBy** - 一旦在函式上使用,則只有提到的呼叫者才能呼叫此函式。

  • **onlyAfter** - 一旦在函式上使用,則只能在某個時間段後才能呼叫該函式。

  • **costs** - 一旦在函式上使用,則只有在提供特定值的情況下,呼叫者才能呼叫此函式。

示例

pragma solidity ^0.5.0;

contract Test {
   address public owner = msg.sender;
   uint public creationTime = now;

   modifier onlyBy(address _account) {
      require(
         msg.sender == _account,
         "Sender not authorized."
      );
      _;
   }
   function changeOwner(address _newOwner) public onlyBy(owner) {
      owner = _newOwner;
   }
   modifier onlyAfter(uint _time) {
      require(
         now >= _time,
         "Function called too early."
      );
      _;
   }
   function disown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) {
      delete owner;
   }
   modifier costs(uint _amount) {
      require(
         msg.value >= _amount,
         "Not enough Ether provided."
      );
      _;
      if (msg.value > _amount)
         msg.sender.transfer(msg.value - _amount);
   }
   function forceOwnerChange(address _newOwner) public payable costs(200 ether) {
      owner = _newOwner;
      if (uint(owner) & 0 == 1) return;        
   }
}

Solidity - 合約

Solidity 中的合約類似於 C++ 中的類。合約具有以下屬性。

  • **建構函式** - 使用 constructor 關鍵字宣告的特殊函式,每個合約將執行一次,並在建立合約時呼叫。

  • **狀態變數** - 每個合約的變數,用於儲存合約的狀態。

  • **函式** - 每個合約的函式,可以修改狀態變數以更改合約的狀態。

可見性限定符

以下是合約函式/狀態變數的各種可見性限定符。

  • **external** - 外部函式旨在由其他合約呼叫。它們不能用於內部呼叫。要在合約內呼叫外部函式,需要使用 this.function_name() 呼叫。狀態變數不能標記為 external。

  • **public** - 公共函式/變數可以在外部和內部使用。對於公共狀態變數,Solidity 會自動建立一個 getter 函式。

  • **internal** - 內部函式/變數只能在內部或由派生合約使用。

  • **private** - 私有函式/變數只能在內部使用,即使派生合約也不可以使用。

示例

pragma solidity ^0.5.0;

contract C {
   //private state variable
   uint private data;
   
   //public state variable
   uint public info;

   //constructor
   constructor() public {
      info = 10;
   }
   //private function
   function increment(uint a) private pure returns(uint) { return a + 1; }
   
   //public function
   function updateData(uint a) public { data = a; }
   function getData() public view returns(uint) { return data; }
   function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
//External Contract
contract D {
   function readData() public returns(uint) {
      C c = new C();
      c.updateData(7);         
      return c.getData();
   }
}
//Derived Contract
contract E is C {
   uint private result;
   C private c;
   
   constructor() public {
      c = new C();
   }  
   function getComputedResult() public {      
      result = compute(3, 5); 
   }
   function getResult() public view returns(uint) { return result; }
   function getData() public view returns(uint) { return c.info(); }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。執行合約的各種方法。對於 E.getComputedResult() 後跟 E.getResult() 顯示 -

輸出

0: uint256: 8

Solidity - 繼承

繼承是擴充套件合約功能的一種方法。Solidity 支援單繼承和多繼承。以下是關鍵要點。

  • 派生合約可以訪問所有非私有成員,包括內部方法和狀態變數。但使用 this 不允許。

  • 允許函式重寫,前提是函式簽名保持不變。如果輸出引數不同,則編譯將失敗。

  • 我們可以使用 super 關鍵字或使用超類名稱來呼叫超類的函式。

  • 在多重繼承的情況下,使用 super 呼叫函式優先考慮派生程度最高的合約。

示例

pragma solidity ^0.5.0;

contract C {
   //private state variable
   uint private data;
   
   //public state variable
   uint public info;

   //constructor
   constructor() public {
      info = 10;
   }
   //private function
   function increment(uint a) private pure returns(uint) { return a + 1; }
   
   //public function
   function updateData(uint a) public { data = a; }
   function getData() public view returns(uint) { return data; }
   function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
//Derived Contract
contract E is C {
   uint private result;
   C private c;
   constructor() public {
      c = new C();
   }  
   function getComputedResult() public {      
      result = compute(3, 5); 
   }
   function getResult() public view returns(uint) { return result; }
   function getData() public view returns(uint) { return c.info(); }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。執行合約的各種方法。對於 E.getComputedResult() 後跟 E.getResult() 顯示 -

輸出

0: uint256: 8

Solidity - 建構函式

建構函式是使用 **constructor** 關鍵字宣告的特殊函式。它是一個可選函式,用於初始化合約的狀態變數。以下是建構函式的關鍵特徵。

  • 合約只能有一個建構函式。

  • 建立合約時會執行一次建構函式程式碼,它用於初始化合約狀態。

  • 建構函式程式碼執行後,最終程式碼將部署到區塊鏈。此程式碼包括公共函式和可透過公共函式訪問的程式碼。僅由建構函式使用的建構函式程式碼或任何內部方法都不包含在最終程式碼中。

  • 建構函式可以是 public 或 internal。

  • 內部建構函式將合約標記為抽象。

  • 如果未定義建構函式,則合約中存在預設建構函式。

pragma solidity ^0.5.0;

contract Test {
   constructor() public {}
}
  • 如果基類有帶引數的建構函式,則每個派生類都必須傳遞它們。

  • 可以使用以下方法直接初始化基類建構函式 -

pragma solidity ^0.5.0;

contract Base {
   uint data;
   constructor(uint _data) public {
      data = _data;   
   }
}
contract Derived is Base (5) {
   constructor() public {}
}
  • 可以使用以下方法間接初始化基類建構函式 -

pragma solidity ^0.5.0;

contract Base {
   uint data;
   constructor(uint _data) public {
      data = _data;   
   }
}
contract Derived is Base {
   constructor(uint _info) Base(_info * _info) public {}
}
  • 不允許同時使用直接和間接方法初始化基類建構函式。

  • 如果派生合約未將引數傳遞給基類建構函式,則派生合約將變為抽象。

Solidity - 抽象合約

抽象合約是指至少包含一個沒有實現的函式的合約。此類合約用作基類合約。通常,抽象合約包含已實現的函式和抽象函式。派生合約將實現抽象函式,並在需要時使用現有函式。

如果派生合約未實現抽象函式,則此派生合約將被標記為抽象。

示例

嘗試以下程式碼以瞭解抽象合約如何在 Solidity 中工作。

pragma solidity ^0.5.0;

contract Calculator {
   function getResult() public view returns(uint);
}
contract Test is Calculator {
   function getResult() public view returns(uint) {
      uint a = 1;
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

輸出

0: uint256: 3

Solidity - 介面

介面類似於抽象合約,並使用 **interface** 關鍵字建立。以下是介面的關鍵特徵。

  • 介面不能有任何帶實現的函式。

  • 介面的函式只能是 external 型別。

  • 介面不能有建構函式。

  • 介面不能有狀態變數。

  • 介面可以有列舉、結構體,可以使用介面名稱點表示法訪問它們。

示例

嘗試以下程式碼來了解Solidity中介面的工作原理。

pragma solidity ^0.5.0;

interface Calculator {
   function getResult() external view returns(uint);
}
contract Test is Calculator {
   constructor() public {}
   function getResult() external view returns(uint){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

注意 - 在點選部署按鈕之前,從下拉選單中選擇測試。

輸出

0: uint256: 3

Solidity - 庫

庫類似於合約,但主要用於重用。庫包含其他合約可以呼叫的函式。Solidity 對庫的使用有一些限制。以下是Solidity 庫的關鍵特性。

  • 如果庫函式不修改狀態,則可以直接呼叫它們。這意味著只有純函式或檢視函式可以從庫外部呼叫。

  • 庫不能被銷燬,因為它被認為是無狀態的。

  • 庫不能有狀態變數。

  • 庫不能繼承任何元素。

  • 庫不能被繼承。

示例

嘗試以下程式碼來了解庫在Solidity中的工作原理。

pragma solidity ^0.5.0;

library Search {
   function indexOf(uint[] storage self, uint value) public view returns (uint) {
      for (uint i = 0; i < self.length; i++) if (self[i] == value) return i;
      return uint(-1);
   }
}
contract Test {
   uint[] data;
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function isValuePresent() external view returns(uint){
      uint value = 4;
      
      //search if value is present in the array using Library function
      uint index = Search.indexOf(data, value);
      return index;
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

注意 - 在點選部署按鈕之前,從下拉選單中選擇測試。

輸出

0: uint256: 3

使用For迴圈

指令using A for B; 可用於將庫A的庫函式附加到給定的型別B。這些函式將使用呼叫方型別作為其第一個引數(使用self標識)。

示例

嘗試以下程式碼來了解庫在Solidity中的工作原理。

pragma solidity ^0.5.0;

library Search {
   function indexOf(uint[] storage self, uint value) public view returns (uint) {
      for (uint i = 0; i < self.length; i++)if (self[i] == value) return i;
      return uint(-1);
   }
}
contract Test {
   using Search for uint[];
   uint[] data;
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function isValuePresent() external view returns(uint){
      uint value = 4;      
      
      //Now data is representing the Library
      uint index = data.indexOf(value);
      return index;
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

注意 - 在點選部署按鈕之前,從下拉選單中選擇測試。

輸出

0: uint256: 3

Solidity - 彙編

Solidity提供了一個選項,可以使用匯編語言在Solidity原始碼中編寫內聯彙編。我們還可以編寫獨立的彙編程式碼,然後將其轉換為位元組碼。獨立彙編是Solidity編譯器的中間語言,它將Solidity程式碼轉換為獨立彙編,然後轉換為位元組碼。我們可以使用與內聯彙編中相同的語言在獨立彙編中編寫程式碼。

內聯彙編

內聯彙編程式碼可以與Solidity程式碼庫交織在一起,以便對EVM進行更細粒度的控制,尤其是在編寫庫函式時使用。

彙編程式碼寫在assembly { ... } 程式碼塊中。

示例

嘗試以下程式碼來了解庫在Solidity中的工作原理。

pragma solidity ^0.5.0;

library Sum {   
   function sumUsingInlineAssembly(uint[] memory _data) public pure returns (uint o_sum) {
      for (uint i = 0; i < _data.length; ++i) {
         assembly {
            o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
         }
      }
   }
}
contract Test {
   uint[] data;
   
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function sum() external view returns(uint){      
      return Sum.sumUsingInlineAssembly(data);
   }
}

使用Solidity 第一個應用程式章節中提供的步驟執行上述程式。

注意 - 在點選部署按鈕之前,從下拉選單中選擇測試。

輸出

0: uint256: 15

Solidity - 事件

事件是合約的可繼承成員。當事件被髮出時,它會將傳遞的引數儲存在交易日誌中。這些日誌儲存在區塊鏈上,並且可以使用合約地址訪問,直到合約存在於區塊鏈上。生成的事件在合約內部不可訪問,即使是建立和發出它們的合約也不行。

可以使用event關鍵字宣告事件。

//Declare an Event
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);

//Emit an event
emit Deposit(msg.sender, _id, msg.value);

示例

嘗試以下程式碼來了解事件在Solidity中的工作原理。

首先建立一個合約併發出一個事件。

pragma solidity ^0.5.0;

contract Test {
   event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
   function deposit(bytes32 _id) public payable {      
      emit Deposit(msg.sender, _id, msg.value);
   }
}

然後在JavaScript程式碼中訪問合約的事件。

var abi = /* abi as generated using compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceiptContract = ClientReceipt.at("0x1234...ab67" /* address */);

var event = clientReceiptContract.Deposit(function(error, result) {
   if (!error)console.log(result);
});

它應該列印類似於以下內容的詳細資訊:

輸出

{
   "returnValues": {
      "_from": "0x1111...FFFFCCCC",
      "_id": "0x50...sd5adb20",
      "_value": "0x420042"
   },
   "raw": {
      "data": "0x7f...91385",
      "topics": ["0xfd4...b4ead7", "0x7f...1a91385"]
   }
}

Solidity - 錯誤處理

Solidity提供了各種用於錯誤處理的函式。通常,當發生錯誤時,狀態會回滾到其原始狀態。其他檢查是為了防止未經授權的程式碼訪問。以下是錯誤處理中使用的一些重要方法:

  • assert(bool condition) - 如果條件不滿足,此方法呼叫會導致無效的操作碼,並且對狀態所做的任何更改都會回滾。此方法用於內部錯誤。

  • require(bool condition) - 如果條件不滿足,此方法呼叫會回滾到原始狀態。- 此方法用於輸入或外部元件中的錯誤。

  • require(bool condition, string memory message) - 如果條件不滿足,此方法呼叫會回滾到原始狀態。- 此方法用於輸入或外部元件中的錯誤。它提供了一個選項來提供自定義訊息。

  • revert() - 此方法中止執行並回滾對狀態所做的任何更改。

  • revert(string memory reason) - 此方法中止執行並回滾對狀態所做的任何更改。它提供了一個選項來提供自定義訊息。

示例

嘗試以下程式碼來了解錯誤處理在Solidity中的工作原理。

pragma solidity ^0.5.0;

contract Vendor {
   address public seller;
   modifier onlySeller() {
      require(
         msg.sender == seller,
         "Only seller can call this."
      );
      _;
   }
   function sell(uint amount) public payable onlySeller { 
      if (amount > msg.value / 2 ether)
         revert("Not enough Ether provided.");
      // Perform the sell operation.
   }
}

當呼叫revert時,它將返回如下所示的十六進位制資料。

輸出

0x08c379a0                     // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data
廣告

© . All rights reserved.