以太坊開發實戰學習-Solidity初學(一)

區塊鏈火熱,做爲程序猿的我,固然也不能袖手旁觀,一位資深技術開發朋友曾笑說:這是屌絲程序猿改變命運爲數很少的機會之一。因此,從今天開始,就要步入區塊鏈的開發大潮中。

1、合約開發流程

語言:使用 node.js 開發該項目
大概流程:css

合約代碼編寫(Solidity)-> 合約編譯(solc)-> 合約部署(web3)html

開發語言及工具:

  • 區塊鏈節點:ganache-cli
  • 基礎環境:node
  • 合約開發語言:Solidity
  • 合約編譯器:solc
  • 合約訪問庫:web3.js

2、基礎環境安裝

  • 一、安裝 node.js
  • 二、安裝 ganache-cli
sudo npm install -g ganache-cli

運行:前端

ganache-cli

輸出:node

➜  ~ ganache-cli
Ganache CLI v6.1.0 (ganache-core: 2.1.0)

Available Accounts
==================
(0) 0x2c0e940b0732a3b49cb7dccc26e14cb4801dd1c3
(1) 0x65afabcf1fdb19ef88f8433af90136de56e7e412
(2) 0x65111c1fa94e15e8e3bdedb466004f67d6b46bab
(3) 0xfa44030a4216193d19a811267528e86cf1851e48
(4) 0xc29473dca76a2ebbb8b1badf6a8093c11b56ea84
(5) 0x06e55addeef67a46015e2790be1ada1deb3c9c70
(6) 0xc1ec7f3d08692d0bdd70d6ab3d5701f22f53a521
(7) 0x42e52cbb5e226ef8c2c9bf54737b87ccf94ebb08
(8) 0x8cebfdb948266022d2499118b0989b290d146d4c
(9) 0x17b791127c57dff3eb31cc203e404536ef7e0ba7

Private Keys
==================
(0) 3bb508f1c2c35083f7d69466830067c6582e4464ba61daffc947bb1aa98618e9
(1) fc06e722c10cd80b1b5b43355f81363dcbe6dcc8d3c59387f69c68ce99f36c53
(2) 07f37ed746ba88da289eaa780d6155d9fee456106d85169ad92a526c22192695
(3) 2619b581c083d20ff84db2688f4a9d836206ee37e993bc8cb1e089ad68c8673f
(4) c3f61de226b5d5c06cb941f93a2a3ec321dabc53a8fb68bee64d3aed5bc130e6
(5) f86e7b7e7a9cf7532004694cb22997ac521567b7c8e480dbee23e426ed787234
(6) 2035f13d8d64109f21e4eb32970e5934cddcd27bc55439634f49d4479c7abe77
(7) 3395049c4f8749b17e154c47199fa42ce538ed051b6240afc55f49d30406a4f0
(8) 976f56be1b1cd9f5c420a3fdb71eb3a8c3875a7bd3fba20c342389ba97b0a165
(9) a2a7a190ee76cdb0675b8af773fba55187ff4a0fc6c1e1021e717d19e0d591ee

HD Wallet
==================
Mnemonic:      result casino this poverty sleep joy toy sort onion spider bind evolve
Base HD Path:  m/44'/60'/0'/0/{account_index}

Listening on localhost:8545

ganache 默認會自動建立 10 個帳戶,每一個帳戶有 100 個以太幣(ETH:Ether)。 能夠把帳戶視爲銀行帳戶,以太幣就是以太坊生態系統中的貨幣。git

面輸出的最後一句話,描述了節點仿真器的監聽地址和端口爲localhost:8545,在使用 web3.js 時,須要傳入這個地址來告訴web3js庫應當鏈接到哪個節點。web

3、合約設計

咱們使用 Solidity 語言來編寫合約。若是你熟悉面向對象的開發和JavaScript,那麼學習Solidity 應該很是簡單。能夠將合約類比於OOP的類:合約中的屬性用來聲明合約的狀態,而合約中的方法則提供修改狀態的訪問接口。算法

重點:數據庫

  • 合約狀態是持久化到區塊鏈上的,所以對合約狀態的修改須要消耗以太幣。
  • 只有在合約部署到區塊鏈的時候,纔會調用構造函數,而且只調用一次。
  • 與 web 世界裏每次部署代碼都會覆蓋舊代碼不一樣,在區塊鏈上部署的合約是不可改變的,也就是說,若是你更新 合約並再次部署,舊的合約仍然會在區塊鏈上存在,而且合約的狀態數據也依然存在。新的部署將會建立合約的一 個新的實例。

4、合約語法

從最基本的開始入手:npm

Solidity 的代碼都包裹在合約裏面. 一份合約就是以太應幣應用的基本模塊, 全部的變量和函數都屬於一份合約, 它是你全部應用的起點.編程

一份名爲 HelloWorld 的空合約以下:

contract HelloWorld {

}

一、版本指令

全部的 Solidity 源碼都必須冠以 "version pragma" — 標明 Solidity 編譯器的版本. 以免未來新的編譯器可能破壞你的代碼。

例如: pragma solidity ^0.4.19; (當前 Solidity 的最新版本是 0.4.19).

綜上所述, 下面就是一個最基本的合約 — 每次創建一個新的項目時的第一段代碼:

contract.sol 合約文件

// 1. 這裏寫版本指令
pragma solidity ^0.4.19;

// 2. 這裏創建智能合約
contract HelloWorld {

}

實戰演習1:

爲了創建咱們的殭屍部隊, 讓咱們先創建一個基礎合約,稱爲 ZombieFactory

  • 創建一個版本爲 0.4.19,咱們的合約基於這個版本的編譯器。
  • 創建一個空合約 ZombieFactory

Contract.sol

pragma solidity ^0.4.19; // 1. 這裏寫版本指令

// 2. 這裏創建智能合約
contract ZombieFactory {

}

二、狀態變量和整數

狀態變量是被永久地保存在合約中。也就是說它們被寫入以太幣區塊鏈中. 想象成寫入一個數據庫。

示例:

contract Example {
  // 這個無符號整數將會永久的被保存在區塊鏈中
  uint myUnsignedInteger = 100;
}

在上面的例子中,定義 myUnsignedInteger 爲 uint 類型,並賦值100。

無符號整數: uint

uint 無符號數據類型, 指其值不能是負數,對於有符號的整數存在名爲 int 的數據類型。

注: Solidity中, uint 其實是 uint256 代名詞, 一個256位的無符號整數。你也能夠定義位數少的uints — uint8uint16uint32, 等…… 但通常來說你願意使用簡單的 uint, 除非在某些特殊狀況下,這咱們後面會講。

實戰演練2

咱們的殭屍DNA將由一個十六位數字組成。

  • 定義 dnaDigitsuint 數據類型, 並賦值 16

Contract.sol

pragma solidity ^0.4.19; // 1. 這裏寫版本指令

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3.定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;
}

三、數學運算

在 Solidity 中,數學運算很直觀明瞭,與其它程序設計語言相同:

  • 加法: x + y
  • 減法: x - y,
  • 乘法: x * y
  • 除法: x / y
  • 取模 / 求餘: x % y (例如, 13 % 5 餘 3, 由於13除以5,餘3)

Solidity 還支持 乘方操做 (如:x 的 y次方) // 例如: 5 ** 2 = 25

uint x = 5 ** 2; // equal to 5^2 = 25

實戰演練3

爲了保證咱們的殭屍的DNA只含有16個字符,咱們先造一個uint數據,讓它等於10^16。這樣一來之後咱們能夠用模運算符 % 把一個整數變成16位。

  • 創建一個uint類型的變量,名字叫dnaModulus, 令其等於 10dnaDigits 次方。

Contract.sol

pragma solidity ^0.4.19; // 1. 這裏寫版本指令

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;
}

四、結構體

有時你須要更復雜的數據類型,Solidity 提供了 結構體:

struct Person {
  uint age;
  string name;
}

結構體容許你生成一個更復雜的數據類型,它有多個屬性。

注:咱們剛剛引進了一個新類型, string。 字符串用於保存任意長度的 UTF-8 編碼數據。 如: string greeting = "Hello world!"

實戰演練4

在咱們的程序中,咱們將建立一些殭屍!每一個殭屍將擁有多個屬性,因此這是一個展現結構體的完美例子。

  • 1.創建一個struct 命名爲 Zombie
  • 2.咱們的 Zombie 結構體有兩個屬性: name (類型爲 string), 和 dna (類型爲 uint)。

Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
}

五、數組

若是你想創建一個集合,能夠用 數組這樣的數據類型. Solidity 支持兩種數組: 靜態 數組和動態 數組:

// 固定長度爲2的靜態數組:
uint[2] fixedArray;
// 固定長度爲5的string類型的靜態數組:
string[5] stringArray;
// 動態數組,長度不固定,能夠動態添加元素:
uint[] dynamicArray;

你也能夠創建一個 結構體類型的數組 例如,上一節提到的 Person:

Person[] people; // dynamic Array, we can keep adding to it

記住:狀態變量被永久保存在區塊鏈中。因此在你的合約中建立動態數組來保存成結構的數據是很是有意義的。

公共數組

你能夠定義 public 數組, Solidity 會自動建立 getter 方法. 語法以下:

Person[] public people;
其它的合約能夠從這個數組讀取數據(但不能寫入數據),因此這在合約中是一個有用的保存公共數據的模式。

實戰演練5

爲了把一個殭屍部隊保存在咱們的APP裏,而且可以讓其它APP看到這些殭屍,咱們須要一個公共數組。

  • 建立一個數據類型爲 Zombie 的結構體數組,用 public 修飾,命名爲:zombies.

Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
    
    // 6.數組類型爲結構體的公共數組
    Zombie[] public zombies;
}

六、定義函數

在 Solidity 中函數定義的句法以下:

function eatHamburgers(string _name, uint _amount) {

}

這是一個名爲 eatHamburgers 的函數,它接受兩個參數:一個 string類型的 和 一個 uint類型的。如今函數內部仍是空的。

注:: 習慣上函數裏的變量都是以( _)開頭 (但不是硬性規定) 以區別全局變量。咱們整個教程都會沿用這個習慣。

咱們的函數定義以下:

eatHamburgers("vitalik", 100);

實戰演練6

在咱們的應用裏,咱們要能建立一些殭屍,讓咱們寫一個函數作這件事吧!

  • 創建一個函數 createZombie。 它有兩個參數: _name (類型爲string), 和 _dna (類型爲uint)。

暫時讓函數空着——咱們在後面會增長內容。
Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
    
    // 6.數組類型爲結構體的公共數組
    Zombie[] public zombies;
    
    // 7.建立函數
    function createZombie(string _name, uint _dna){
        
    }
}

七、使用結構體和數組

建立新的結構體

還記得上個例子中的 Person 結構嗎?

struct Person {
  uint age;
  string name;
}

Person[] public people;

如今咱們學習建立新的 Person 結構,而後把它加入到名爲 people 的數組中.

// 建立一個新的Person:
Person satoshi = Person(172, "Satoshi");

// 將新建立的satoshi添加進people數組:
people.push(satoshi);

你也能夠兩步並一步,用一行代碼更簡潔:

people.push(Person(16, "Vitalik"));
注: array.push() 在數組的 尾部 加入新元素 ,因此元素在數組中的順序就是咱們添加的順序, 如:
uint[] numbers;
numbers.push(5);
numbers.push(10);
numbers.push(15);
// numbers is now equal to [5, 10, 15]

實戰演練7

讓咱們建立名爲createZombie的函數來作點兒什麼吧。

  • 1.在函數體裏新建立一個 Zombie, 而後把它加入 zombies 數組中。 新建立的殭屍的 namedna,來自於函數的參數。
  • 2.讓咱們用一行代碼簡潔地完成它。

Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
    
    // 6.數組類型爲結構體的公共數組
    Zombie[] public zombies;
    
    // 7.建立函數
    function createZombie(string _name, uint _dna){
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
}

八、私有 / 公共函數

Solidity 定義的函數的屬性默認公共。 這就意味着任何一方 (或其它合約) 均可以調用你合約裏的函數。

顯然,不是何時都須要這樣,並且這樣的合約易於受到攻擊。 因此將本身的函數定義爲私有是一個好的編程習慣,只有當你須要外部世界調用它時纔將它設置爲公共
如何定義一個私有的函數呢?

uint[] numbers;
function _addToArray(uint _number) private {
  numbers.push(_number);
}

這意味着只有咱們合約中的其它函數纔可以調用這個函數,給 numbers 數組添加新成員。

能夠看到,在函數名字後面使用關鍵字 private 便可。和函數的參數相似,私有函數的名字用(_)起始。

實戰演練8

咱們合約的函數 createZombie 的默認屬性是公共的,這意味着任何一方均可以調用它去建立一個殭屍。 我們來把它變成私有吧!

  • 1.變 createZombie 爲私有函數,不要忘記遵照命名的規矩哦!

Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
    
    // 6.數組類型爲結構體的公共數組
    Zombie[] public zombies;
    
    /*
    // 7.建立函數
    function createZombie(string _name, uint _dna){
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    */
    
     // 7.建立函數(改成私有方法)
    function _createZombie(string _name, uint _dna) private {
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    
   
}

九、函數的更多屬性

本節中咱們將學習函數的返回值和修飾符。

返回值

要想函數返回一個數值,按以下定義:

string greeting = "What's up dog";

function sayHello() public returns (string) {
  return greeting;
}

Solidity 裏,函數的定義裏可包含返回值的數據類型(如本例中 string)。

函數的修飾符view,returns

上面的函數實際上沒有改變 Solidity 裏的狀態,即,它沒有改變任何值或者寫任何東西。
這種狀況下咱們能夠把函數定義爲 view, 意味着它只能讀取數據不能更改數據:

function sayHello() public view returns (string) {}

Solidity 還支持 pure 函數, 代表這個函數甚至都不訪問應用裏的數據,例如:

function _multiply(uint a, uint b) private pure returns (uint) {
  return a * b;
}

這個函數甚至都不讀取應用裏的狀態 — 它的返回值徹底取決於它的輸入參數,在這種狀況下咱們把函數定義爲 pure.

注:可能很難記住什麼時候把函數標記爲 pure/view。 幸運的是, Solidity 編輯器會給出提示,提醒你使用這些修飾符。

實戰演練9

咱們想創建一個幫助函數,它根據一個字符串隨機生成一個DNA數據。

  • 1.建立一個 private 函數,命名爲 _generateRandomDna。它只接收一個輸入變量 _str (類型 string), 返回一個 uint 類型的數值。
  • 2.此函數只讀取咱們合約中的一些變量,因此標記爲view
  • 3.函數內部暫時留空,之後咱們再添加代碼。

Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
    
    // 6.數組類型爲結構體的公共數組
    Zombie[] public zombies;
    
    /*
    // 7.建立函數
    function createZombie(string _name, uint _dna){
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    */
    
     // 7.建立函數(改成私有方法)
    function _createZombie(string _name, uint _dna) private {
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    
    // 9.函數修飾符 private, view, returns 返回值
    function _generateRandomDna(string _str) private view returns (uint){
        
      }
    
  
}

十、Keccak256 和 類型轉換

散列函數 Keccak256

如何讓 _generateRandomDna 函數返回一個全(半) 隨機的 uint?

Ethereum 內部有一個散列函數keccak256,它用了SHA3版本。一個散列函數基本上就是把一個字符串轉換爲一個256位的16進制數字。字符串的一個微小變化會引發散列數據極大變化。

這在 Ethereum 中有不少應用,可是如今咱們只是用它造一個僞隨機數。

示例:

//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaab");
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256("aaaac");

顯而易見,輸入字符串只改變了一個字母,輸出就已經天壤之別了。

注: 在區塊鏈中安全地產生一個隨機數是一個很難的問題, 本例的方法不安全,可是在咱們的Zombie DNA算法裏不是那麼重要,已經很好地知足咱們的須要了。

類型轉換

有時你須要變換數據類型。例如:

uint8 a = 5;
uint b = 6;
// 將會拋出錯誤,由於 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 咱們須要將 b 轉換爲 uint8:
uint8 c = a * uint8(b);
上面, a * b 返回類型是 uint, 可是當咱們嘗試用 uint8 類型接收時, 就會形成潛在的錯誤。若是把它的數據類型轉換爲 uint8, 就能夠了,編譯器也不會出錯。

實戰演練10

_generateRandomDna 函數添加代碼! 它應該完成以下功能:

  • 1.第一行代碼取 _strkeccak256 散列值生成一個僞隨機十六進制數,類型轉換爲 uint, 最後保存在類型爲 uint 名爲 rand 的變量中。
  • 2.咱們只想讓咱們的DNA的長度爲16位 (還記得 dnaModulus?)。因此第二行代碼應該 return 上面計算的數值對 dnaModulus 求餘數(%)。

Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
    
    // 6.數組類型爲結構體的公共數組
    Zombie[] public zombies;
    
    /*
    // 7.建立函數
    function createZombie(string _name, uint _dna){
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    */
    
     // 7.建立函數(改成私有方法)
    function _createZombie(string _name, uint _dna) private {
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    
    // 9.函數修飾符 private, view, returns 返回值
    function _generateRandomDna(string _str) private view returns (uint){
        // 10.散列並取模
        uint rand = uint(keccak256(_str));  // 注意這裏須要將string類型轉爲uint類型
        return rand % dnaModulus;
    }
        
      }

}

十一、綜合應用

咱們就快完成咱們的隨機殭屍製造器了,來寫一個公共的函數把全部的部件鏈接起來。

寫一個公共函數,它有一個參數,用來接收殭屍的名字,以後用它生成殭屍的DNA。

實戰演練11

  • 1.建立一個 public 函數,命名爲createRandomZombie. 它將被傳入一個變量 _name (數據類型是 string)。 (注: 定義公共函數 public 和定義一個私有 private 函數的作法同樣)。
  • 2.函數的第一行應該調用 _generateRandomDna 函數,傳入 _name 參數, 結果保存在一個類型爲 uint 的變量裏,命名爲 randDna
  • 3.第二行調用 _createZombie 函數, 傳入參數: _namerandDna
  • 4.整個函數應該是4行代碼 (包括函數的結束符號 } )。

Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
    
    // 6.數組類型爲結構體的公共數組
    Zombie[] public zombies;
    
    /*
    // 7.建立函數
    function createZombie(string _name, uint _dna){
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    */
    
     // 7.建立函數(改成私有方法)
    function _createZombie(string _name, uint _dna) private {
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    
    // 9.函數修飾符 private, view, returns 返回值
    function _generateRandomDna(string _str) private view returns (uint){
        // 10.散列並取模
        uint rand = uint(keccak256(_str));  // 注意這裏須要將string類型轉爲uint類型
        return rand % dnaModulus;
    }
        
     
     // 十一、事件
    function createRandomZombie(string _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

十二、事件

咱們的合約幾乎就要完成了!讓咱們加上一個事件.

事件 是合約和區塊鏈通信的一種機制。你的前端應用「監聽」某些事件,並作出反應。

示例:

// 這裏創建事件
event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public {
  uint result = _x + _y;
  //觸發事件,通知app
  IntegersAdded(_x, _y, result);
  return result;
}

你的 app 前端能夠監聽這個事件。JavaScript 實現以下:

YourContract.IntegersAdded(function(error, result) { 
  // 幹些事
}

實戰演練12

咱們想每當一個殭屍創造出來時,咱們的前端都能監聽到這個事件,並將它顯示出來。

  • 1.定義一個 事件 叫作 NewZombie。 它有3個參數: zombieId (uint)name (string), 和 dna (uint)
  • 2.修改 _createZombie 函數使得當新殭屍造出來並加入zombies數組後,生成事件NewZombie
  • 3.須要定義殭屍idarray.push() 返回數組的長度類型是uint - 由於數組的第一個元素的索引是 0array.push() - 1 將是咱們加入的殭屍的索引。 zombies.push() - 1 就是 id,數據類型是 uint。在下一行中你能夠把它用到 NewZombie 事件中。

Contract.sol

// 1. 這裏寫版本指令
pragma solidity ^0.4.19; 

// 2. 這裏創建智能合約
contract ZombieFactory {

  // 12.這裏創建事件
  event NewZombie(uint zombieId, string name, uint dna);

  // 3. 定義 dnaDigits 爲 uint 數據類型, 並賦值 16
  uint dnaDigits = 16;

  // 4. 10 的 dnaDigits 次方
  uint dnaModulus = 10 ** dnaDigits;

   // 5.結構體定義
   struct Zombie {
        string name;
        uint dna;

    }
    
    // 6.數組類型爲結構體的公共數組
    Zombie[] public zombies;
    
    /*
    // 7.建立函數
    function createZombie(string _name, uint _dna){
         // 8.使用結構體和數組(初始化全局數組)
        zombies.push(Zombie(_name, _dna));
    }
    */
    
     // 7.建立函數(改成私有方法)
    function _createZombie(string _name, uint _dna) private {
         // 8.使用結構體和數組(初始化全局數組)
         // zombies.push(Zombie(_name, _dna));
         
        // 十二、數組長度減一就是當前的數組ID
        uint id = zombies.push(Zombie(_name, _dna)) - 1;

        // 十二、這裏觸發事件
        NewZombie(id, _name, _dna);
    }
    
    // 9.函數修飾符 private, view, returns 返回值
    function _generateRandomDna(string _str) private view returns (uint){
        // 10.散列並取模
        uint rand = uint(keccak256(_str));  // 注意這裏須要將string類型轉爲uint類型
        return rand % dnaModulus;
    }
        
     
     // 十一、綜合函數
    function createRandomZombie(string _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

5、Web3.js

咱們的 Solidity 合約完工了! 如今咱們要寫一段 JavaScript 前端代碼來調用這個合約。

以太坊有一個 JavaScript 庫,名爲Web3.js

在後面的課程裏,咱們會進一步地教你如何安裝一個合約,如何設置Web3.js。 可是如今咱們經過一段代碼來了解 Web3.js 是如何和咱們發佈的合約交互的吧。

若是下面的代碼你不能全都理解,不用擔憂。

// 下面是調用合約的方式:
var abi = /* abi是由編譯器生成的 */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* 發佈以後在以太坊上生成的合約地址 */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` 能訪問公共的函數以及事件

// 某個監聽文本輸入的監聽器:
$("#ourButton").click(function(e) {
  var name = $("#nameInput").val()
  //調用合約的 `createRandomZombie` 函數:
  ZombieFactory.createRandomZombie(name)
})

// 監聽 `NewZombie` 事件, 而且更新UI
var event = ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  generateZombie(result.zombieId, result.name, result.dna)
})

// 獲取 Zombie 的 dna, 更新圖像
function generateZombie(id, name, dna) {
  let dnaStr = String(dna)
  // 若是dna少於16位,在它前面用0補上
  while (dnaStr.length < 16)
    dnaStr = "0" + dnaStr

  let zombieDetails = {
    // 前兩位數構成頭部.咱們可能有7種頭部, 因此 % 7
    // 獲得的數在0-6,再加上1,數的範圍變成1-7
    // 經過這樣計算:
    headChoice: dnaStr.substring(0, 2) % 7 + 1,
    // 咱們獲得的圖片名稱從head1.png 到 head7.png

    // 接下來的兩位數構成眼睛, 眼睛變化就對11取模:
    eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
    // 再接下來的兩位數構成衣服,衣服變化就對6取模:
    shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
    //最後6位控制顏色. 用css選擇器: hue-rotate來更新
    // 360度:
    skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),
    eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),
    clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),
    zombieName: name,
    zombieDescription: "A Level 1 CryptoZombie",
  }
  return zombieDetails
}

咱們的 JavaScript 所作的就是獲取由zombieDetails 產生的數據, 而且利用瀏覽器裏的 JavaScript 神奇功能 (咱們用 Vue.js),置換出圖像以及使用CSS過濾器。在後面的課程中,你能夠看到所有的代碼。

6、Truffle框架學習

Truffle 是一個DApp開發框架,它簡化了去中心化應用的構建和管理。


注:本教程是匯智網-以太坊DApp開發入門學習筆記,感興趣的同窗能夠去學習。

相關文章
相關標籤/搜索