2018年5月5日 星期六

Solidity: 合約環境設置、撰寫、修改


開發環境

要使用 Ethereum(乙太幣) 在瀏覽器上進行交易,需要先安裝 Metamask 插件,才可以使用,本編記錄之 Solidity 智能合約亦需要用 Ethereum 來支付上傳費用及資料,因此也要安裝。

Metamask: https://metamask.io/


Account / Metamask


安裝 Metamask 外掛後,可以打開介面來測試看看,可以 Create Account 一或多個。



然後將網路切換為測試網路:

點擊左上角的 Network 改為 Rinkeby Test Network 或其他 Test Network,Main Ethereum Network 是真實乙太坊的網路,本文使用 Rinkeby Test Network 。

切換後,還需要先取得測試用的 ETH (乙太幣) 才能進行下一步,使用 https://faucet.rinkeby.io/
此服務來取得測試用的 ETH,則請先在自己的 Google Plus 把 Metamask 上的錢包地址貼上,並發文,然後把該文章的網址貼上此服務,即可獲得 ETH。


Solidity 文件格式


欲撰寫智能合約,可以利用各式 Solidity 的 IDE 來撰寫,本文使用 remix 這個 IDE:

https://remix.ethereum.org/

文件第一行必須是定義該合約所使用的版本為何,例如像是:


pragma solidity ^0.4.0;

接著,此文件便可以定義一個合約的結構,可形容為是一個執行的程式,所以定義一個合約結構之後,還需要加入一個執行的進入點,此進入點名稱必須和合約名稱一樣:

pragma solidity ^0.4.0;

contract kinoticket { //定義本合約結構
    function kinoticket() public { //定義合約進入點
        //do something...
    }
}

該合約的進入點名稱是一個函式 (function),並也可以加入多個進入點所需要的參數,像是:

pragma solidity ^0.4.0;

contract kinoticket {
    function kinoticket(int quantity, string location) public {
        //do something...
    }
}

然後,合約結構中,只能在函式外(結構全域) 來定義變數,而且需要在第一個函式宣告之前定義,像是:

pragma solidity ^0.4.0;

contract kinoticket {
    
    int public quantity;
    string public location;
    
    string private filmName;
    
    function kinoticket(int quantity, string location) public {
        
        filmName = "Ready Player One (VieShow Cinemas K-FE21)";
        
        kinoticket.quantity = quantity;
        kinoticket.location = location;
        
        //do something...
    }
}

接著,可以嘗試執行該合約,並觀察裡面的變數值。 執行合約前,需要先編譯:


從 Remix 右邊側欄可以看到 Compile 有 Start to compile 可以執行,而且如果嫌手動編譯太麻煩,可以勾選 Auto compile 來自動編譯。

編譯完,從右邊側欄選擇 Run ,並且一開始先選擇環境 (Environment) 為 JavaScript VM 放在本地執行。

(其餘選項: Account 則是之後付款或存 ETH 進合約的帳號,以及 Gas limit 為限制 gas(可以說是手續費) 大小,避免超量支付造成個人損失,以及 value 是在之後使用付費 function 時,設定投入的金額, wei 是單位,可以選擇不同的 Ethereum 計算單位)


選擇好環境後,在下面第二個區塊,有 Create 按鈕,旁邊請先填上剛才在合約進入點這個函式所定義的參數: [int quantity, string location], 在此輸入 1, "kaohsiung" ,其是以逗號 ( , ) 來區分下一個參數。

參數完成後,按下 Create 則可以在本地執行。


稍微等待一下,就會在最下面的區塊顯示目前正在執行的合約,若想確認變數值,可以點擊 location, quantity 這兩個按鈕,就會顯示值。

若要取消執行,則按下 x 或是垃圾桶圖案清除所有執行的合約。

Solidity 型別/封裝方式

變數型別


Solidity 變數型別常見有以下幾種:

  • string
  • int
    • int8 
    • int16
    • int32
    • ... (二次方數)
    • int256 (寫 int 時預設使用)
    • uint (不帶號整數)
    • uint8
    • ... (二次方數)
    • uint256
  • fixed (浮點數)
    • fixed (預設使用)
    • unfixed (不帶號浮點數)
  • bool
  • address (ETH 地址格式)
  • mapping (mapping ([型別] => [型別]) public [變數名稱];)
  • byte
  • .... (其他不在此介紹)
補充:

mapping 使用方式:

定義:

設定一個 mapping 的變數, key 是 string 並且指向 value 是一個 int 數值:


mapping (string => int64) public myList;


賦值:


myList["teacher"] = 50;


取資料:


if(myList["teacher"] == true){ ... }

變數封裝方式


定義變數時,像是:

string public contributorName;

則會使用到定義是否公開的形式,全部形式則有:

  • private (不公開)
  • public (公開)

函式封裝方式


定義函數時,同時也會宣告函數的形式,像是:

function deleteAllTicket() public {
} 

則有使用到 public 形式,代表該函數可以被執行的情況,全部的形式則有:


  • public (公開)
  • private  (不公開)


存取方式定義

變數或函式,都可以利用存取方式定義來限制該變數或函式的用法,像是定義變數是公開的常數形式:

string public constant contributorName = "Cathay Pacific";

而函數則像是:

function deleteAllContributors() public constant{
}

全部的存取方式定義有:

  • view (只讀,裡面所有的變數指定,不會影響到全域的變數,只在該函式有效)
  • constant (不可修改)
  • pure (不可讀,不可以修改)
  • payable (被執行時會自動開啟 metamask 付款,付款的錢在 Run-> Value 有定義)


    (要付款時,則使用 Value 中的數量來付款)

Solidity 函式


函式宣告


一標準的函式定義是:

function [函式名稱]([型別 參數],...,[型別 參數]) [封裝形式] [存取型式] [returns] ([回傳值型別],...[回傳值型別]){
        [returns] ([回傳值],...[回傳值]);
}

舉例像是:

function viewContributor(int index)public view returns(string){
        return myContributorsList[index];
}

回傳值設定


回傳值設定是可有可無,如果有回傳值,則要寫上 "returns" 並在後面括弧寫上回傳值按照順序回傳的型別:

... returns (string, int, bool){...


Solidity Event Log


合約也是區塊鏈的一個,在區塊鏈中,變數修改則不會留下紀錄,但在註記方面,則會留下永久、無法抹滅的紀錄,合約則可以寫入該註記紀錄,使得只要參與者一付款或合約上傳人一上傳,就會留下紀錄。

Event Log 可存放資料甚少,因此無法用來存放大量資料,其大小定義請參考官方資料說明,則不在此解釋。

要注意的是, Event Log 無法存放 string 字串,必須是經過 Hex 編碼過的內容,因此第三方服務的存取都要經過 Hex(16進位轉換)。

Event Log 在 Solidity 要先在變數設定區定義一個 event 要存放的參數,定義為:

event [自訂名稱]([型別] [變數], ..., [型別] [變數]);

像是:

event sendLog(bytes32 data, uint balance);

定義好後,則可以直接當成函式執行,像是:

sendLog(轉 16 進位 [this log is very cool.], 2160063);

紀錄就會像是:


Solidity 上傳合約至區塊鏈


若要對目前編譯後的合約進行上傳,請從 Run 切換 Enviroument 至 Injected Web3 ,然後再使用 Create 來進行上傳,此行為會在 remix 內利用 web3.js 針對目前 metamask 選擇的網路進行上傳,因此只要切換網路,就可以在不同的鏈上傳。

上傳手續費取決於程式碼文件,按下 Create 後會請你支付合約費用 (不要在 value 輸入任何值):


上傳後,可以在 metamask 看到處理交易的狀態:


可以點擊或從 https://rinkeby.etherscan.io/ 看到自己處理交易的狀態。



待處理完成後,請從 Etherscan 點擊 To: Contract 連結,到目前合約的頁面。



進去後,選擇 Code 這個欄位,這裡顯示編譯後的內容,因此合約上的值改變,在對應原始碼前都看不懂,然而在 Ethscan 上有提供 Code 對應 Compile 的功能,請在此選擇 Verify and publish:


進入後,在下方所需填入的內容有:



  • Contract Address (預設已填上)
  • Contract Name (請自行命名)
  • Compiler (編譯器版本,此版本需要對照程式編譯器版本,從 Remix 的 Setting 可以看到編譯時使用的版本)

  • Optimization (是否經過代碼優化,也就是壓縮,一般選擇沒有)


拉到最下面驗證,並確認,等待確定對應無誤。



完成後,就可以回到剛才的 Code 頁面,看到此合約的程式碼:


接著,選擇 Read Contract 的功能,則可以看到目前的變數值:


選擇 Event 功能則可以看到 Event Log 的歷史紀錄內容,此內容不會被抹除。

若要修改區塊鏈上的資料,有兩種做法:


  • 從 Remix 上的執行區,修改值後選擇按鈕,付費完就會修改

  • 利用第三方應用程式進行修改


Solidity 串接第三方 API (web3.js)


利用第三方應用程式修改區塊鏈上的內容,可以使用 web3.js ,其原理是利用區塊鏈合約上的
 ABI (類似像 API 的功能) 組成一個資料,在選定修改值後,把 ABI 連同 metamask 付手續費一起上傳,就可以更改。

初始化 web3.js 連接至合約



<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
    <script src="js/web3.min.js"></script>
</head>
<body>
    <script>
        var web3 = new Web3(window.web3.currentProvider);
        var Contract;
        var abi = [{"c...uctor"}];
        var address = '0x17d2...041';
    </script>
</body>
</html>

其中必要要填入的內容,有 abi 和 address , abi 可以從合約上的 Code -> 下方有 abi 取得:


address 則複製合約的 Ethereum Address ,在合約頁面上方應有。

取得合約上的變數值


此方法必須使用非同步寫法執行,程式碼寫在初始化 web3.js 程式碼下方:

async function showLocation(){
Contract = await new web3.eth.Contract(abi,address);

    //執行區塊鏈上面的內容,如果該名稱是 function ,就會執行 function ,如果是變數,就會顯示變數。 
    Contract.methods.location().call().then(function(data){
        console.log(data); //-> kaohsiung
    });

}

showLocation(); 

如同註解表示,從 method 呼叫的方法,如果在合約上是變數,就會顯示變數,如過是一個 function ,就會執行 function。



修改合約上的值


方法一樣需要使用非同步寫法執行:


async function send(){

    //抓出 metamask 所有使用的帳號 (非同步方法), 不加入 await 就會直接跳過,導致失敗。
    var accounts = await web3.eth.getAccounts();
       
    var str = "tomorrow live in Hsinchu."

    //假設合約有 function editLocation(string loc)...;
    Contract.methods.editLocation(str)
    .send({from:accounts[0]}) //0 是抓出 metamask 第一個帳號進行操作
    .then(function(data){
        console.log(data); //回傳的資料
    });
        
}
send(); 

web3.js 工具


欲顯示合約上的 string 值或是 eth gas 單位轉換,在 web3.js 皆有提供,詳情可以參考官方文件

//將 string 值轉成 hex
web3.utils.utf8ToHex(str);

//將 hex 值轉成 string
web3.utils.hexToUtf8(str);

//轉換 0.15 wei 單位變成 ether 單位
web3.utils.toWei(0.15, "ether")


Reference:
http://www.tryblockchain.org/solidity-ConstantStateVariables-%E5%B8%B8%E7%8A%B6%E6%80%81%E5%8F%98%E9%87%8F.html
http://web3js.readthedocs.io/en/1.0/web3-utils.html
https://quip.com/0mG7AKVjzr9l

沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014