【區塊鏈】以太坊Soliddity編程:智能合約實現之基本語法

以太坊Soliddity編程:智能合約實現

基本數據類型

1. Solidity類型:

  • solidity爲靜態類型語言
  • 所有變量均需要預定義
  • 和大多數語言一樣,提供幾個基本類型和基本類型組成的複雜類型
  • 和現代語言相比,solidity僅覆蓋最常見的類型。

2. Solidityt基本數據類型

  • 布爾類型
  1. 布爾:真或假
  2. 操作符:!(邏輯非)
  3. &&(邏輯與)
  4. || (邏輯或)
  5. == (相等)
  6. != (不等)
  • 整型
  1. int / uint : 是有符號和無符號的整數
  2. uint8到uint256步長8(從8到256位的無符號整數)
  3. uint和int分別是uint256和int256的別名
  • 地址
  1. 地址:20字節(一個以太坊地址)
  2. 可表示用戶賬號,合約地址
  3. 操作符:≤、<、==、!=,≥,>
  4. 地址成員: 賬戶餘額(balance)
  5. 地址成員:發送(transfer),從合約發起方向某個地址轉入以太幣(單位是wei)
    地址
  • 註釋
  • 字符串常量
  1. 字符串常量用雙引號或者單引號括起來,如“abc”,'abc’均可
  2. 字符串常量可以隱式轉換位bytes
  3. 字符串常量支持轉義符,如\n,\xNN(16進制)and \nNNNN(Unicode)

補充:
整型操作符:

  • 比較: ≤,<,==,!=,≥,>(計量布爾量)
  • 位操作符:&,!,^(位異或),~(位取反)
  • 算術操作符:+,-,*,/,%(取餘數),**(冪次方)
  • 除以0操作,會報異常
    枚舉類型:
  • 用戶自定義類型,基本同C++等語言的定義
  • 枚舉類型中的枚舉值到整型可顯示轉換,但不能隱式轉換
  • 枚舉類型不能爲空,至少應當包含一個值
    枚舉類型

基本類型轉換

  1. 分爲隱式轉換和顯示轉換,如字符串轉爲整型等
  2. 隱式轉換:如果運算符支持兩邊不同的類型,編譯器會嘗試隱式轉換類型,包括賦值
  3. 隱式轉換需要能保證不會丟失數據,且語義可通
  4. 顯示轉換:如果編譯器不允許隱式的自動轉換,但你知道轉換沒問題時,可以進行轉換
  5. 不正確的轉換會帶來錯誤
    基本類型轉換

array類型

數組

  • 數組是可以在編譯時固定大小的,也可以是動態的
  • 對於storage存儲數組來說,成員類型可以是任意的(也可以是其他數組,映射或結構)
  • 對於memory數組來說,成員類型不能是一個映射,如果是公開可見的函數參數,成員類型是必須是ABI類型的。

數組聲明

  • T[k]:元素類型爲T,固定長度爲k的數組
  • T[]:動態大小的(變長)的數組
  • uint[3][5]:聲明5個uint[3](與大部分語言相同).訪問時,uint[4][0]:第5個數組的第1個元素
  • bytes類似於byte[]

數組成員函數

  • 成員函數:length,存放元素的數量
  • 動態length可變
  • 成員函數:push,可在數組的尾部添加一個元素。函數返回新的長度
  • Push目前僅storage變長數組可用
    數組成員函數

數組使用事項

數組作爲返回參數使用

  • 暫時還無法在外部函數中使用數組的數組(多維數組)
  • 暫時無法從外部函數調用返回的動態內容
  • 目前可行的解決辦法是使用較大的靜態尺寸大小的數組

數組創建和初始化

  • 可使用new關鍵字創建或初始化memory的變長數組
  • 但不能通過.length的長度來修改memory數組大小屬性
  • Storage數組可以修改長度
  • 目前暫時是這樣規定的
    數組的創建和初始化
    要點:變長數組須先new初始化,分配空間
    變長數組先new
    要點:變長storage數組可以通過push初始化
    變長storage
    要點:暫不能通過外部函數返回變長數組
    不能通過外部函數返回變長數組
    要點:定長的數組不可直接賦值給變長數組
    定長的數組不可直接賦值給變長數組

mapping類型

映射定義

  • 映射或字典類型,一種鍵值對的映射關係存儲結構
  • 定義方式爲mapping(KeyType=>KeyValue)
  • 鍵的類型一般爲基本數據類型,暫不支持映射、變長數組、結構體等複雜類型。值的類型無限制
  • 映射可以視爲哈希表。所有可能的鍵會被虛擬化的創建,映射到一個類型的默認值
  • 映射表中,我們並不存儲鍵的數據,僅存儲它的keccak256哈希值,用來查找值時使用
  • 映射並沒有長度,鍵集合(或列表),值集合(或列表)這樣的概念。

映射定義的例子:
映射定義的例子
映射的賦值:
映射的賦值

映射使用要點

  1. 映射類型,僅能用來定義狀態變量,或者是在內部函數中作爲storage類型的引用
  2. 可以通過將映射標記爲public,來讓Solidity創建一個訪問器
  3. 訪問映射,需要提供一個鍵值作爲參數
  4. 映射暫未提供迭代輸出的方法

struct類型

結構體

  • Solidity提供struct來定義自定義類型
  • struct可以用於映射和數組中作爲元素
  • 其本身也可以包含映射和數組等類型
  • 但struct內不能有struct。

結構體示例
結構體示例

結構體初始化

直接賦值(按順序)

結構體初始化

命名初始化

命名初始化

忽略mapping

忽略mapping

數據的位置和引用類型

值類型:布爾、整型、地址、定長字節數組

  1. 在傳值時,總是值傳遞,完全拷貝

引用類型:不定長數組、字符串、數組、結構體

  1. 複雜類型,佔用空間較大,在拷貝時佔用空間較大,一般都通過引用傳遞。

引用傳遞

  • 值傳遞,傳遞的是內存的內容
  • 引用傳遞,傳遞的是內存的地址
  • 引用的改變,修改後會改變內存地址對應儲存的值,也就是變量和其引用會同時改變。

數據的位置

  • 複雜/引用類型,如數組(arrays)和結構體(struct)有一個額外的屬性,數據的存儲位置,可選爲memory或storage
  • Memory存儲位置爲內存
  • Storage保存永久記錄,存儲在鏈上

數據的默認位置

  • 基於程序的上下文,大多數時候這樣的選擇是默認的,也通過指定關鍵字storage和memory修改它
  • 函數參數,包括返回的參數,默認是memory
  • 局部複雜變量默認是storage
  • 狀態變量強制爲storage

不同數據位置的賦值

  • 在memory和storage之間,完全拷貝
  • 任何位置的變量,賦值給狀態變量,完全拷貝
  • 狀態變量(storage),賦值storage局部變量,引用傳遞
  • memory的引用類型賦值給另一個memory的引用,不會創建另一個拷貝

注意事項

  1. 不能將memory賦值給局部變量
  2. memory只能用於函數內部
  3. 對於值類型,總是會進行拷貝
  4. storage在區塊鏈中是用key/value的形式存儲,而memory則表現爲字節數組
  5. 值類型的局部變量是存儲在棧上
  6. Gas消耗storage>>memory>stack

不同位置複雜類型賦值圖

不同位置複雜類型賦值圖

常規用法

  • storage爲合約級變量,在合約創建時就確定了,但內容可以被(交易)改變
  • solidity認爲交易就是改變了合約狀態,故合約級變量稱爲“狀態”變量。函數內部只能定義storage的引用
  • memory只能用於函數內部,其聲明EVM在運行時創建一塊內存區域給變量使用
    數據的位置示例

特殊變量和函數

地址相關(Address Related)

  • <address>.balance(uint256): address的餘額,以wei爲單位
  • <address>.transfer(uint256 amount): 從合約(地址)向address發送一定數量的ether,以wei爲單位。
  • <address>.send(uint256 amount) returns (bool): 同transfer。不建議。
    示例:
    地址相關示例

合約相關

  • this:當前合約的類型,可以顯示的轉換爲Address
  • selfdestruct(address recipt):銷燬當前合約,並把它所有資金髮送到給定的地址。
  • 如果一個函數需要進行貨幣操作,必須要帶上payable關鍵字。

數學和加密函數

  • assert(bool condition):如果條件不滿足,拋出異常
  • keccak256(…) returns (bytes32):使用以太坊的(Keccak-256)計算HASH值。常用來做字符串相等判別。

特殊變量及函數

  1. msg.sender(address) 當前調用發起人的地址
  2. msg.value(uint) 這個消息所附帶的貨幣量,單位爲wei
  3. tx.origin(address) 交易的發送者。不建議
  4. msg.data(bytes) 完整的調用數據(calldata)。
  5. now(uint) 當前塊的時間戳。

時間單位

  • seconds、minutes、hours、days、weeks、years均可做爲後綴,並進行相互轉換,默認是seconds爲單位。
  • 後綴不能用於變量
    時間單位

貨幣單位

  • 一個字面量的數字,可以使用後綴wei、finney、szabo或ether來在不同面額中轉換
  • 不含任何後綴的默認單位是wei
    單位換算
    貨幣單位換算
    貨幣使用示例
    貨幣使用示例

實戰:課程積分

項目需求

  • 爲了活躍課程的氣氛,促進同學之間的交流,決定建立課程積分體系:
  1. 每個同學都能領到100個課程積分
  2. 爲別的同學答疑解惑,可以從對方那裏獲得一定的課程積分
  3. 課程結束後,積分最高的同學獲得小紅花

典型設計

基於sql數據庫設計實現:

用戶表(User)

  • userId: 用戶id
  • email: 用戶email

  • 用戶積分表(UserCoin)
  • userId: 用戶id
  • coin:積分數量

  • 用戶積分交易表i(UserTrade)
  • userId: 發送方用戶id
  • toUserId: 接收方用戶id
  • coin:發送數量

不足

  1. 發放積分方式不透明,需要老師的信譽背書
  2. 積分交易數據不透明,可能出現刷分現象
  3. 服務器可能掛了…

Dapp設計的優勢

  1. 去信任。代碼透明,積分體系和發放形式完全公開,無需老師信用背書
  2. 去中心。數據透明,交易數據公共可讀,無法隨意篡改,公共監督。不用擔心機器崩潰。
  3. 隱私保護。暫不需要。

Dapp數據結構設計

用狀態變量實現數據結構
用戶表(User)

struct User {
	address userId; // 用戶id
}

User[] userList;

用戶積分表(UserCoin)

struct UserCoin {
	address userId; //用戶id
	uint coin; //積分數量
}

UserCoin[] userCoinList;

用戶積分交易表(UserTrade)

struct UserTrade {
	address userId; // 發送方userid
	address toUserId; // 接收方userid
	uint coin; //發送數量
}

UserTrade[] userTradeList;

Dapp數據結構設計改進原則

  • 基本思路同“SQL數據庫映射到KV數據庫”
  • 根據查詢/索引需求,在基本數據結構上,建立mapping
  • 爲節省存儲,去掉冗餘數據

功能需求1

  1. 每個同學都能領100個課程積分
  2. 函數 getCoin()
  3. 功能:由合約爲調用者發送100個積分
  4. 查詢需求:只能領一次,需要根據合約和調用者地址,查詢對應的交易記錄。

功能需求2

  1. 同學之間可以互送積分
  2. 函數 sendCoin()
  3. 功能:調用者向一個地址發送積分
  4. 查詢需求:一個用戶地址擁有的積分

Dapp數據結構的改進

用戶積分交易

struct UserTrade {
	address userId; // 發送方userid
	address toUserId; // 接收方userid
	uint coin; //發送數量
}

UserTrade[] userTradeList;

查詢需求:根據“合約和調用者地址”,查詢交易

mapping (address => mapping(address=>UserTrade[])) trans;

去除冗餘數據

mapping (address => mapping(address=>uint[])) trans;

Dapp數據結構設計結果

用戶

address[] userList;
mapping (address => uint8) userDict;

用戶積分(address爲key)

mapping (address => uint) balances;

用戶積分交易表(address+address爲key)

mapping (address => mapping (address => uint[])) trans;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章