1. 程式人生 > >智能合約編程語言-solidity快速入門(上)

智能合約編程語言-solidity快速入門(上)

run 訪問器 balance 部分 abcde commit 通過 使用 images

solidity簡介

本文默認讀者已掌握至少一種面向對象編程語言,所以文中一些概念會借助其他語言進行類比。

solidity是用於實現智能合約的一種面向合約的高級編程語言,solidity受到C++、Python和JavaScript的影響,被設計為可運行在以太坊虛擬機(EVM)上,所以用戶無需擔心代碼的可移植性和跨平臺等問題。solidity是一種靜態類型的語言,支持繼承、庫引用等特性,並且用戶可自定義復雜的結構類型。

目前嘗試 Solidity 編程的最好的方式是使用 Remix (由於是網頁IDE可能加載起來需要一定的時間)。Remix 是一個基於 Web 的 IDE,它可以讓你編寫 Solidity 智能合約,然後部署並運行該智能合約,它看起來是這樣子的:

技術分享圖片

也可以使用sublime或vs code等編輯器編寫 Solidity 代碼,然後復制粘貼到Remix上部署運行。

solidity官網地址如下:

https://solidity.readthedocs.io/en/latest/index.html


合約文件

本小節我們來說說合約文件,眾所周知任何語言所編寫的代碼都需要存儲在一個文件裏,並且該文件都有一個特定的後綴名,我們一般將這種文件稱之為代碼文件。

solidity代碼文件的後綴名為.sol,但我們通常會把使用solidity編寫的文件稱之為合約文件,一個合約文件通常會包含四個部分,其實與我們平時所編寫其他語言的代碼文件是類似的,如下圖所示:

技術分享圖片

版本聲明的代碼需寫在合約文件的開頭,接著可以根據實際情況導入一些合約,所謂導入合約也就類似於其他面向對象的語言導入某個類的概念。然後就是聲明一個合約,在合約裏編寫具體的代碼,其實這裏的合約與我們所熟悉的類的概念基本上是一樣的,可以暫時將它們當做同一個東西。

我們先來對一個較為完整的合約代碼進行一個預覽,在之後會對代碼中的每個部分進行逐一介紹:

// 版本聲明
pragma solidity ^0.4.0;

// 導入一個合約
import "solidity_for_import.sol";

// 定義一個合約
contract ContractTest {
    // 定義一個無符號整型變量
    uint a;

    // 定義一個事件
    event Set_A(uint a);

    // 定義一個函數
    function setA(uint x) public {
        a = x;
        // 觸發一個事件
        emit Set_A(x);
    }

    // 定義一個具有返回值的函數
    function getA() public returns (uint) {
        return a;
    }

    // 自定義一個結構類型
    struct Pos {
        // 定義一個有符號整型變量
        int lat;
        int lng;
    }

    // 定義一個地址類型,每個合約都運行在一個特定的地址上
    address public addr;

    // 定義一個函數修改器
    modifier owner () {
        require(msg.sender == addr);
        _;
    }

    // 讓函數使用函數修改器
    function mine() public owner {
        a += 1;
    }
}

這裏對函數修改器做一個簡單的說明:

函數修改器的概念類似於python中的裝飾器,其核心目的都是給函數增加函數內沒有定義的功能,也就是對函數進行增強

從以上代碼中,可以看到owner 函數修改器裏定義了一句條件代碼,其意義為:

msg.sender等於addr地址變量時,才繼續往下執行,因為這個require函數是solidity校驗條件用的,若不符合條件就會拋出異常

mine函數使用了owner函數修改器後,那麽mine函數在執行之前,會先執行owner函數修改器裏的條件代碼,也就是說當msg.sender等於addr成立的話,才會執行mine函數裏a += 1;的代碼,否則就不會執行。從中也可以看出函數修改器裏的_;語句,其實表示的就是mine函數裏的代碼,如此一來在不修改mine函數的前提下,給mine函數增加了額外的功能。


solidity 類型

Solidity是一種靜態類型語言,意味著每個變量(本地或狀態變量)需要在編譯時指定變量的類型(或至少可以推導出類型),Solidity提供了一些基本類型可以用來組合成復雜類型。

Solidity和大多數語言一樣,有兩種類型:

  • 值類型(Value Type) - 變量在賦值或傳參時,總是進行值拷貝。
  • 引用類型(Reference Types)

solidity所包含的值類型如下:
技術分享圖片

註:其中標紅的是最常用的類型

官網關於solidity類型的文檔地址如下:

https://solidity.readthedocs.io/en/latest/types.html

1.布爾類型取值範圍是true和false,使用bool關鍵字進行聲明,聲明方式如下:

// 版本聲明
pragma solidity ^0.4.0;

// 定義一個合約
contract ContractTest {
    bool b1 = true;
    bool b2 = false;
}   

2.solidity中有兩種整型的定義方式,一種是無符號整型,另一種則是有符號整型。並且支持關鍵字uint8 到 uint256 (以8步進),uint 和 int 默認對應的是 uint256 和 int256。如下示例:

// 版本聲明
pragma solidity ^0.4.0;

// 定義一個合約
contract ContractTest {
    // 定義一個無符號的整型變量
    uint a;
    // 定義一個有符號的整型變量
    int i;
}

solidity常量

在solidity裏使用constant關鍵字來聲明常量,但並非所有的類型都支持常量,當前支持的僅有值類型和字符串:

pragma solidity ^0.4.0;

contract C {
    uint constant x = 32**22 + 8;
    string constant text = "abc";
    bytes32 constant myHash = keccak256("abc");
}

在solidity中還可以將函數聲明為常量,該函數的返回值就是常量值,這類函數將承諾自己不修改區塊鏈上任何狀態:

// 定義有理數常量
function testLiterals() public constant returns (int) {
    return 1;
}

// 定義字符串常量
function testStringLiterals() public constant returns (string) {
    return "string";
}

// 定義16進制常量,以關鍵字hex打頭,後面緊跟用單或雙引號包裹的字符串,內容是十六進制字符串
function testHexLiterals() public constant returns (bytes2) {
    return hex"abcd";
}

有理數常量函數裏的運算可以是任意精度的,不會有溢出的問題:

// 定義有理數常量
function testLiterals() public constant returns (int) {
    return 1859874861811128585416.0 + 123.0;
}

科學符號也支持,基數可以是小數,但指數必須是整數,如下:

// 定義有理數常量
function testLiterals() public constant returns (int) {
    return 2e10;
}

solidity地址類型

solidity中使用address關鍵字聲明地址類型變量,該類型屬於值類型,地址類型主要用於表示一個賬戶地址,一個以太坊地址的長度為20字節的16進制數,地址類型也有成員,地址是所有合約的基礎。

地址類型的主要成員:

  • 屬性:balance,用來查詢賬戶余額
  • 函數:transfer(),用來轉移以太幣(默認以wei為單位)

代碼示例如下:

pragma solidity ^0.4.7;

contract AddrTest {

    // payable關鍵字定義一個可接受以太幣的函數
    function deposit() public payable {

    }

    // 查詢賬戶余額
    function getBalance() public constant returns (uint) {
        return this.balance;
    }

    // 轉移以太幣
    function transferEther(address towho) public {
        towho.transfer(10);
    }
}

然後我們將這段代碼復制粘貼到remix中編譯運行看看,首先需要在Compile選項卡中將代碼進行編譯:
技術分享圖片

編譯成功後,到Run選項卡中,部署該合約:
技術分享圖片

部署成功後,可以查看到合約中的各個函數,並且只需要點擊就可以運行指定的函數:
技術分享圖片

此時我們來點擊執行一下getBalance函數:
技術分享圖片

可以看到,此時該合約的賬戶余額為0,現在我們來存儲10個wei的以太幣到合約中:
技術分享圖片

此時再執行getBalance函數,合約余額為10個wei:
技術分享圖片

然後我們再來看看轉移/發送以太幣的transferEther函數,此時我們這個合約地址的余額為10個wei,當我將這10個wei的以太轉移到另一個地址後,當前合約的余額為0:
技術分享圖片

在solidity中一個能通過地址合法性檢查(address checksum test)的十六進制常量就會被認為是地址,如:

0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF

而不能通過地址合法性檢查的39到41位長的十六進制常量,會提示一個警告,被視為普通的有理數常量。

關於賬戶地址的合法性檢查定義參考如下提案:

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md


solidity數組

在上文中我們提到Solidity 類型分為值類型和引用類型,以上小節介紹了常見的值類型,接下來會介紹一下引用類型。

引用類型是一個復雜類型,占用的空間通常超過256位, 拷貝時開銷很大,因此我們需要考慮將它們存儲在什麽位置,是存儲在memory(內存,數據不是永久存在)中還是存儲在storage(永久存儲在區塊鏈)中。

所有的復雜類型如數組和結構體都有一個額外的屬性:數據的存儲位置(data location),可為memory和storage。根據上下文的不同,大多數時候數據存儲的位置有默認值,也可以通過指定關鍵字storage和memory修改它。

函數參數(包含返回的參數)默認是memory。而局部復雜類型變量(local variables)和狀態變量(state variables) 默認是storage。局部變量即部作用域(越過作用域即不可被訪問,等待被回收)的變量,如函數內的變量,狀態變量則是合約內聲明的公有變量。

除此之外,還有一個存儲位置是:calldata,用來存儲函數參數,是只讀的,不會永久存儲的一個數據位置。外部函數的參數(不包括返回參數)被強制指定為calldata。效果與memory差不多。還有一個存儲位置是:calldata,用來存儲函數參數,是只讀的,不會永久存儲的一個數據位置。外部函數的參數(不包括返回參數)被強制指定為calldata。效果與memory差不多。

數組是一種典型的引用類型,在solidity中數組的定義方式如下:

  • T[k]:元素類型為T,固定長度為k的數組
  • T[]:元素類型為T,長度可動態調整的數組
  • bytes和string 是一種特殊的數組,string 可轉為 bytes,而bytes則類似於byte[]

數組類型有兩個主要成員:

  • 屬性:length
  • 函數:push()

具體的示例代碼如下:

pragma solidity ^0.4.7;

contract ArrayTest {
    // 定義一個無符號整型的變長數組
    uint[] public numbers = [1, 2, 3];

    // 定義一個字符串
    string str = "abcdefg";

    function getNumbersLength() public returns (uint) {
        // 往數組中添加一個元素
        numbers.push(4);

        // 返回數組的長度
        return numbers.length;
    }

    function getStrLength() public constant returns (uint) {
        // 將字符串轉換為bytes並返回長度
        return bytes(str).length;
    }

    function getFirst() public constant returns (byte) {
        // 將字符串轉換為bytes後,通過下標訪問元素
        return bytes(str)[0];
    }

    function newMemory(uint len) public constant returns (uint) {
        // 定義一個定長數組並通過memory指定數組的存儲位置
        uint[] memory memoryArr = new uint[] (len);
        return memoryArr.length;
    }

    function changeFirst(uint[3] _data) public constant returns (uint[3]) {
        // 通過索引操作元素
        _data[0] = 0;
        return _data;
    }
}

solidity結構體和映射

Solidity提供struct關鍵字來定義自定義類型也就是結構體,自定義的類型屬於引用類型,如果學習過go語言的話應該對其不會陌生。如下示例:

// 版本聲明
pragma solidity ^0.4.7;

// 定義一個合約
contract ContractTest {
    // 聲明一個結構體
    struct Funder {
        address addr;
        uint amount;
    }

    // 將自定義的結構體聲明為狀態變量
    Funder funder;

    // 使用結構體
    function newFunder() public {
        funder = Funder({addr: msg.sender, amount: 10});
    }
}

solidity擁有映射類型,映射類型是一種鍵值對的映射關系存儲結構,有點類似於python語言中的字典。定義方式為mapping(_KeyType => _KeyValue)。鍵類型允許除映射、變長數組、合約、枚舉、結構體外的幾乎所有類型值類型沒有任何限制,可以為任何類型包括映射類型。

映射可以被視作為一個哈希表,所有可能的鍵會被虛擬化的創建,映射到一個類型的默認值(二進制的全零表示)。在映射表中,並不存儲鍵的數據,僅僅存儲它的keccak256哈希值,這個哈希值在查找值時需要用到。正因為此,映射是沒有長度的,也沒有鍵集合或值集合的概念。

映射類型有一點比較特殊,它僅能用來作為狀態變量,或在內部函數中作為storage類型的引用。

可以通過將映射標記為public,來讓Solidity創建一個訪問器。通過提供一個鍵值做為參數來訪問它,將返回對應的值。映射的值類型也可以是映射,使用訪問器訪問時,要提供這個映射值所對應的鍵,不斷重復這個過程。

示例代碼如下:

// 版本聲明
pragma solidity ^0.4.7;

// 定義一個合約
contract ContractTest {
    // 定義一個映射類型,key類型為address,value類型為uint
    mapping(address => uint) public balances;

    function updateBalance(uint newBalance) public {
        // msg.sender作為鍵,newBalance作為值,將這對鍵值添加到該映射中
        balances[msg.sender] = newBalance;
    }
}

智能合約編程語言-solidity快速入門(上)