??Solidity是傳說中編寫智能合約的腳本語言,運行在EVM中;用以解決區(qū)塊鏈中的任務執(zhí)行。一個目前看起來還非常稚嫩的語言。這里做一個結構性介紹。并據此展開詳細的說明。
Solidity語言特點
- Solidity 是一門面向合約的、為實現(xiàn)智能合約而創(chuàng)建的高級編程語言。
- 這門語言受到了 C++,Python 和 Javascript 語言的影響,設計的目的是能在以太坊虛擬機(EVM)上運行。
- Solidity 是靜態(tài)類型語言,支持繼承、庫和復雜的用戶定義類型等特性。
- 使用Solidity 語言,可以為各種應用創(chuàng)建合約
- 投票;
- 眾籌;
- 秘密競價(盲拍);
- 多重簽名的錢包;
- 以及其他應用;
Slidity語言結構
源代碼文件
源代碼文件與其他語言一樣,使用文本文件,擴展名使用sol。
-
文件的的組織操作系統(tǒng)的文件一樣,使用目錄組織;
- 文件之間使用import引用,引用可以指定目錄名。這個與ES6語法類似。
-
import "filename";- 此語句將從 “filename” 中導入所有的全局符號到當前全局作用域中。
-
import * as symbolName from "filename";- 創(chuàng)建一個新的全局符號 symbolName,其成員均來自 "filename" 中全局符號。
-
import {symbol1 as alias, symbol2} from "filename";- 創(chuàng)建新的全局符號 alias 和 symbol2,分別從 "filename" 引用 symbol1 和 symbol2 。
-
import "filename" as symbolName;- 條語句等同于 import * as symbolName from "filename";。
目錄
- 支持"."與".."
- ".":當前目錄
- "..":上級目錄
- 在編譯器支持目錄重映射:
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;- 編譯:
solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol
文件結構
-
頭
- 版本申明
- import
-
合約
contract 合同名 {}
-
注釋
- 與javascript一樣,行注釋與塊注釋。
///**/
例子
- other.sol
pragma solidity ^0.6.1;
contract Other {
uint value;
}
- solidity.sol
pragma solidity ^0.6.1;
import "./other.sol";
// 行注釋
contract MySol is Other {
/**
塊注釋
*/
uint age;
}
- 編譯
solcjs --abi solidity.sol other.sol

多文件編譯
合約contract
-
在 Solidity 中,合約類似于面向對象編程語言中的類。 每個合約中可以包含
- 狀態(tài)變量;
- 函數(shù);
- 函數(shù)修飾器;
- 事件;
- 結構類型;
- 枚舉類型 ;
合約可以繼承的
合約定義
contract 合約名 [is] 父合約{
// 1. 狀態(tài)變量;
// 2. 函數(shù);
// 3. 事件;
// 4. 結構類型;
// 5. 枚舉類型;
}
狀態(tài)變量
contract MyContract {
uint varState; // 狀態(tài)變量
// ...
}
- 狀態(tài)變量:
- 類型 存儲名;
- 類型見后面說明;
函數(shù)與函數(shù)修飾
- 合約中可執(zhí)行單元
contract Purchase {
address public seller;
modifier onlySeller() { // 修飾器
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
function abort() public onlySeller { // 函數(shù)與修飾器使用
// ...
}
}
事件
- 事件是能方便地調用以太坊虛擬機日志功能的接口。
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // 事件
function bid() public payable {
// ...
emit HighestBidIncreased(msg.sender, msg.value); // 觸發(fā)事件
}
}
結構類型
- 結構是可以將幾個變量分組的自定義類型
- 用戶自定義復合類型
contract Ballot {
struct Voter { // 結構
uint weight;
bool voted;
address delegate;
uint vote;
}
// .....
}
枚舉類型
- 舉可用來創(chuàng)建由一定數(shù)量的“常量值”構成的自定義類型.
contract Purchase {
enum State { Created, Locked, Inactive } // 枚舉
}
合同繼承
- 使用is關鍵字實現(xiàn)。
pragma solidity ^0.4.16;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
// 使用 is 從另一個合約派生。派生合約可以訪問所有非私有成員,包括內部函數(shù)和狀態(tài)變量,
// 但無法通過 this 來外部訪問。
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
- 主要:
- 繼承包含抽象類與接口的定義。
抽象合約
- 語法沒有什么差異,主要在函數(shù)的實現(xiàn)上:
- 包含抽象函數(shù) // 沒有實現(xiàn)函數(shù)體的函數(shù)就是抽象函數(shù)
- 抽象合約:
- 主要是只有抽象函數(shù)的合約就是接口合約。
- 實現(xiàn)函數(shù)與抽象函數(shù)混雜的就是抽象合約。
contract Feline { // 可以包含實現(xiàn)的就是抽象合約,這里只有一個抽象函數(shù),實際也是接口合約。
function utterance() public returns (bytes32);
}
contract Cat is Feline {
function utterance() public returns (bytes32) { return "miaow"; }
}
庫
- 使用libarary定義庫,定義好的庫可以在合約中使用,下面是官方的例子:
- 庫的語法與合約差不多,合約可以使用庫中數(shù)據與函數(shù)。
pragma solidity ^0.4.16;
library Set {
// 我們定義了一個新的結構體數(shù)據類型,用于在調用合約中保存數(shù)據。
struct Data { mapping(uint => bool) flags; }
// 注意第一個參數(shù)是“storage reference”類型,因此在調用中參數(shù)傳遞的只是它的存儲地址而不是內容。
// 這是庫函數(shù)的一個特性。如果該函數(shù)可以被視為對象的方法,則習慣稱第一個參數(shù)為 `self` 。
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // 已經存在
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // 不存在
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
}
contract C {
Set.Data knownValues;
function register(uint value) public {
// 不需要庫的特定實例就可以調用庫函數(shù),
// 因為當前合約就是“instance”。
require(Set.insert(knownValues, value));
}
// 如果我們愿意,我們也可以在這個合約中直接訪問 knownValues.flags。
}
數(shù)據類型與數(shù)據
數(shù)據的定義
-
語法:
類型 變量名 = 初始值- 變量名遵循Javascript的命名規(guī)則。
-
刪除變量
- delete 變量名;
-
常量:
- constant:
int constant a = 2000;
數(shù)據類型與字面值
布爾類型與布爾值
- 類型關鍵字:
bool - 布爾值:
true與false
整數(shù)類型與整數(shù)值
- 類型關鍵值:
- int / uint (有符號與無符號)
- 整數(shù)也分字節(jié)大?。簡挝皇俏?,8位一個字節(jié),根據對齊規(guī)則,必須是8的倍數(shù)。
- int8/uint8
- 。。。
- int256/uint256 = int/uint
- 整數(shù)值:
- 只支持10與16進制
- 普通法表示:122
- 不能前綴0。
- 16進制使用hex前綴轉換:hex"001122FF" 或者 0x前綴。
- 科學記數(shù)法表示:1e10
- 指數(shù)必須是整數(shù),不支持小數(shù)。
- 普通法表示:122
- 只支持10與16進制
小數(shù)類型與小數(shù)值
-
小數(shù)類型關鍵字:fixed / ufixed
- 有符號與無符號小數(shù)
- 小數(shù)還可以自帶精度表示:
- ufixedMxN / fixedMxN (M表示表示該類型占用的位數(shù),N表示可用的小數(shù)位數(shù))
- M也必須是8的倍數(shù),最大256。
- ufixedMxN / fixedMxN (M表示表示該類型占用的位數(shù),N表示可用的小數(shù)位數(shù))
-
小數(shù)值:
- 使用小數(shù)點表示小數(shù)。 與整數(shù)一樣,分成普通表示與科學表示。
-
例子:
- ufixed32x2 score = 12.45;
-
注意:
- Solidity 還沒有完全支持定長浮點型??梢月暶鞫ㄩL浮點型的變量,但不能給它們賦值或把它們賦值給其他變量。。
地址類型與地址值
- 地址類型存儲一個 20 字節(jié)的值(以太坊地址的大?。?。
- 地址類型也有成員變量,并作為所有合約的基礎。
- 地址類型關鍵字:address
- 地址值表示:0x開頭的16進制表示。
- 地址變量包含多個成員(成員屬性與成員函數(shù)),用來訪問地址相關信息:
- balance :balance 屬性來查詢一個地址的余額
- send/transfer :transfer 函數(shù)向一個地址發(fā)送 以太幣Ether (以 wei 為單位):
- 備注:地址的所有成員:
-
<address>.balance (uint256):- 以Wei為單位的地址類型的余額。
-
<address>.transfer(uint256 amount):- 向地址類型發(fā)送數(shù)量為amount的Wei,失敗時拋出異常,發(fā)送 2300 gas 的礦工費,不可調節(jié)。
-
<address>.send(uint256 amount) returns (bool):- 向地址類型 發(fā)送數(shù)量為 amount 的 Wei,失敗時返回 false,發(fā)送 2300 gas 的礦工費用,不可調節(jié)。
-
<address>.call(...) returns (bool):- 發(fā)出低級函數(shù) CALL,失敗時返回 false,發(fā)送所有可用 gas,可調節(jié)。
-
<address>.callcode(...) returns (bool):- 發(fā)出低級函數(shù) CALLCODE,失敗時返回 false,發(fā)送所有可用 gas,可調節(jié)。
-
<address>.delegatecall(...) returns (bool):- 發(fā)出低級函數(shù) DELEGATECALL,失敗時返回 false,發(fā)送所有可用 gas,可調節(jié)。
-
數(shù)組類型與值表示
- 數(shù)組關鍵字:
類型[] - 數(shù)組的創(chuàng)建:
new - 例子:
pragma solidity ^0.4.16;
contract C {
function f(uint len) public pure {
uint[] memory a = new uint[](7);
a[6] = 8;
}
}
- 兩個特殊的數(shù)組:
- bytes 與 string 等加以 int8[]或者 byte[]
- 例子:
pragma solidity ^0.4.16;
contract C {
function f(uint len) public pure {
uint[] a = new uint[](7);
bytes b = new bytes(len); // string b= new string(len)
// 這里我們有 a.length == 7 以及 b.length == len
a[6] = 8;
}
}
-
數(shù)組字面值
[1, 2, 3, 4]
-
數(shù)組變量賦值的注意事項:長度一致
- 下面例子無法賦值:
// 這段代碼并不能編譯。
pragma solidity ^0.4.0;
contract C {
function f() public {
// 這一行引發(fā)了一個類型錯誤,因為 unint[3] memory
// 不能轉換成 uint[] memory。
uint[] x = [uint(1), 3, 4];
}
}
-
數(shù)組的成員
- length屬性:獲取數(shù)組長度,還可以通過這個成員屬性修改數(shù)組的長度(只對存儲有效,為位置在內存的變量無效,參考后面存儲位置的說明)
- push函數(shù):用來向數(shù)組末尾添加數(shù)據
- 這length對字符串數(shù)組無效。
-
多維數(shù)組:
bool[2][3] m_pairsOfFlags;
字符串類型與值表示
-
字符串也是數(shù)組,其字面值表示:
-
"foo":3字節(jié)字符數(shù)組。 - 字符串數(shù)組與bytes數(shù)組可以隱式轉換。
-
-
字符串支持轉移字符
- 字符串字面常數(shù)支持轉義字符,例如
\n,\xNN 和 \uNNNN。\xNN表示一個 16 進制值,最終轉換成合適的字節(jié), 而\uNNNN表示 Unicode 編碼值,最終會轉換為 UTF-8 的序列。
- 字符串字面常數(shù)支持轉義字符,例如
枚舉類型
-
定義枚舉類型
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
-
使用枚舉類型
ActionChoices defaultChoice = ActionChoices.GoStraight;
pragma solidity ^0.4.16;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
結構體
- 結構體是定義新的類型,語法如下:
struct Funder {
address addr;
uint amount;
}
- 結構體不能包含自己。
存儲位置
-
所有的復雜類型,即數(shù)組和結構類型,都有一個額外屬性,“數(shù)據位置”,
- 說明數(shù)據是保存在 內存memory 中還是 存儲storage 中。
- 根據上下文不同,大多數(shù)時候數(shù)據有默認的位置,但也可以通過在類型名后增加關鍵字 storage 或 memory 進行修改。
- 函數(shù)參數(shù)(包括返回的參數(shù))的數(shù)據位置默認是 memory, 局部變量的數(shù)據位置默認是 storage,狀態(tài)變量的數(shù)據位置強制是 storage 。
-
第三種數(shù)據位置calldata
- 這是一塊只讀的,且不會永久存儲的位置,用來存儲函數(shù)參數(shù)。
- 外部函數(shù)的參數(shù)(非返回參數(shù))的數(shù)據位置被強制指定為 calldata ,效果跟 memory 差不多。
例子:
pragma solidity ^0.4.0;
contract C {
uint[] x; // x 的數(shù)據存儲位置是 storage
// memoryArray 的數(shù)據存儲位置是 memory
function f(uint[] memoryArray) public {
x = memoryArray; // 將整個數(shù)組拷貝到 storage 中,可行
var y = x; // 分配一個指針(其中 y 的數(shù)據存儲位置是 storage),可行
y[7]; // 返回第 8 個元素,可行
y.length = 2; // 通過 y 修改 x,可行
delete x; // 清除數(shù)組,同時修改 y,可行
// 下面的就不可行了;需要在 storage 中創(chuàng)建新的未命名的臨時數(shù)組, /
// 但 storage 是“靜態(tài)”分配的:
// y = memoryArray;
// 下面這一行也不可行,因為這會“重置”指針,
// 但并沒有可以讓它指向的合適的存儲位置。
// delete y;
g(x); // 調用 g 函數(shù),同時移交對 x 的引用
h(x); // 調用 h 函數(shù),同時在 memory 中創(chuàng)建一個獨立的臨時拷貝
}
function g(uint[] storage storageArray) internal {}
function h(uint[] memoryArray) public {}
}
映射(key - value)
-
映射類型在聲明語法為
-
mapping(_KeyType => _ValueType)。 - 其中
_KeyType可以是除了映射、變長數(shù)組、合約、枚舉以及結構體以外的幾乎所有類型。 -
_ValueType可以是包括映射類型在內的任何類型。
-
-
映射可以視作哈希表
- 它們在實際的初始化過程中創(chuàng)建每個可能的 key, 并將其映射到字節(jié)形式全是零的值:一個類型的 默認值。
- 然而下面是映射與哈希表不同的地方:
- 在映射中,實際上并不存儲 key,而是存儲它的keccak256哈希值,從而便于查詢實際的值。
類型轉換
- 類型轉換一般分成顯式轉換與隱式轉換:
- 顯式轉換 : 類型(值)
運算符與表達式
布爾運算
! (邏輯非)&& (邏輯與, "and" )|| (邏輯或, "or" )== (等于)!= (不等于)
整數(shù)運算
- 比較運算符:
<= , < , == , != , >= , > (返回布爾值)
- 位運算符:
& , | , ^ (異或), ~ (位取反)
- 算數(shù)運算符:
+ , - , 一元運算 - , 一元運算 + , * , / , % (取余) , ** (冪), << (左移位) , >> (右移位)
小數(shù)運算
- 比較運算符:
<=, <, ==, !=, >=, > (返回值是布爾型)
- 算術運算符:
+, -, 一元運算 -, 一元運算 +, *, /, % (取余數(shù))
地址運算
- 比較運算:
<=, <, ==, !=, >= 和 >
數(shù)組運算
- 比較運算符:
<=, <, ==, !=, >=, > (返回布爾型)
- 位運算符:
&, |, ^ (按位異或), ~ (按位取反), << (左移位), >> (右移位)
- 索引訪問:
如果 x 是 bytesI 類型,那么 x[k] (其中 0 <= k < I)返回第 k 個字節(jié)(只讀)。
- .length
表示這個字節(jié)數(shù)組的長度(對定長只讀).
流程控制
-
JavaScript 中的大部分控制結構在 Solidity 中都是可用的,除了 switch 和 goto。
- if,else,
- while,
- do,
- for,
- break,continue,return,
- ? :
-
注意:
- 用于表示條件的括號不可以被省略,單語句體兩邊的花括號可以被省略。
- 與C和JavaScript不同, Solidity 中非布爾類型數(shù)值不能轉換為布爾類型,因此 if (1) { ... } 的寫法在 Solidity 中 無效 。