---
# System prepended metadata

title: 從以太坊白皮書理解 web 3 概念 - Day9
tags: [' Web 3', it 鐵人賽 30 天, ' ethereum']

---

---
tags: it 鐵人賽 30 天, Web 3, ethereum
---
# 從以太坊白皮書理解 web 3 概念 - Day9

從今天開始到 Day 22 共 13 天

將會以 [CryptoZombie](https://cryptozombies.io/en/course/) 這個 solidity 學習網站

來了解 Solidity 這個語言

學完之後將能夠知道如何透過 Solidity 來開發 Small Contract

## Solidity 簡介

Solidity 語言是 EVM 上能夠運行的一種腳本語言，用來撰寫 Small Contract

在這 13 天的介紹中

將分為成以下大章節：

Smart Contract 基礎資料結構與 Web3.js 互動介面：Day 9 - Day 13

Smart Contract 與去中心化 Oracle 互動： Day 14

Smart Contract 測試與如何建立 Oracle: Day 15 - Day 19 

Smart Contract 周邊系統： Day 20 - Day 21

## Learn Solidity - day 1 建立 ZombieContract 

可以開啟另一個視窗跟著本文一起去實作 

課程連結如下:

[Lession 1: ZombieFactory](https://cryptozombies.io/en/lesson/1/chapter/1)

在開始撰寫 Contract 之前

先理解要實作的需求


### ZombieContract 需求

ZombieContract 是一個生成 Zombie 物件的工廠

每個 Zombie 具有 name 與 dna 屬性

並且具有一個識別子 id 作為一個辨識生成過的編號

name 是一個隨機字串

dna 是由 id 與 name 做雜湊產生出來的字串

根據這些參數每個 Zombie 物件有都是有他獨特的樣貌

### Contract 基礎要件

#### Solidity 編譯版本號

在每個 Contract 的代碼前都必須要標注編譯的 Solidity 版本號

因為每個版本都有其特別的功能及不同的資料結構支援

舉例來說：

要撰寫一個由 solidity 版本於 0.5.0 及 0.6.0 之間的 Contract

必須在上方宣告如下：

```solidity=
pramga solidity >=0.5.0 <0.6.0;
```

#### Contract 本體

Contract 本體如同物件導向語言裏面，對於物件會宣告一個建立物件的類別

當作建立物件的藍本。

Contract 也是一樣，會宣告一個開頭為 contract 帶有名稱的腳本區段

當作建立該 Contract 的腳本。

如下：

建立一個 ZombieFactory 的空腳本

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {}
```

#### 狀態變數與整數

在 solidity 裏面，當在 contract 內部宣告一個變數時

這個變數將會永遠寫入 constract 的儲存空間內。代表狀態變數會被寫入區塊鏈上。

舉例來說：
```solidity=
contract Example {
    // myUnsignedInteger 會變成 Contract 內容寫入鏈上
    uint myUnsignedInteger = 100;
}
```

#### Unsigned Integers

在 solidity ， uint 代表這個整數沒有負號，只能存正數。

另外，uint 代表 uint256 的別名。
其他 bit 位數的無號數還有 uint8, uint16, uint32 等等。 

在 ZombieFactory 內，需要一個無號數來存儲 dnaDigits 並且設定為 16。

到此 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
}
```

#### 數學運算符號

在 solidity ， 數值運算與一般程式語言一樣：

* 加法: x+y
* 減法: x-y
* 乘法: x*y
* 除法: x/y
* 取餘數： x%y (e.g., 13 % 5 = 3)

另外也支援指數運算

```solidity=
uint x = 5**2; // x = 5*5 = 25
```

為了保證 Zombie 的 DNA 字元只有 16 個字元長

所以建立一個 uint 變數 dnaModulus = dnaDigits的16次方

這樣每次只要把數值 % dnaModulus 就會滿足這個條件

更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
}
```

#### Structs 資料結構

在 solidity 除了基礎的資料結構外，也支援複合型資料結構

透過 struct 來做宣告

舉例如下:

```solidity=
struct Person {
    uint age;
    string name;
}
```

其中 string 是儲存 UTF-8 編碼的字串，也就是每個字元只有 8-bit。

因為每個 Zombie 需要有屬於自己的資料，也就是 dna 跟 name

所以需要建立一個 struct 名稱設定為 Zombie

Zombie 具有兩個屬性:

1. name: 字串代表其名字
2. dna: uint 代表其特徵值


更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
}
```

#### Arrays

當要存儲大量相同類型的資料時

就可以使用 array

在 Solidity ，根據陣列長度是否為固定分成 fixed 與 dynamic

```solidity=
// fixed array 資料集長度固定
uint[2] fixedArray;
// dynamic array 資料集長度不固定
uint[] dynamicArray;
```

#### Public 屬性

狀態資料可以把其存取屬性設定為 public ， solidity 會自動替這個屬性產生 getter，讓其他有用到的地方讀取。


語法如下

```solidity=
Person[] public people;
```

在 ZombieFactory ，需要使用一個 dynamic Array 來紀錄所有產生出來的 Zombie 物件，並且需要讓這個 dynamic Array 存取權限是 public。


更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
    Zombie[] public zombies;
}
```

#### 函式宣告

函式宣告語法如下

```solidity=
function eatHamburgers(string memory _name, uint _amount) public {}
```

函式宣告包含元素如下：

1. 函式名稱： 如上面函式名稱是 eatHamburgers
2. 參數： 參數根據其傳遞方式分為兩種。 pass by value：傳值，只會把值做複製傳入，在函式中修改不影響其原本的值。 pass by reference：傳參考，會把該參數的參考指標傳入，在函式中修改其值，會透過參考指標改到原本的值，對於參考值需要加入 memory 在參數前面。而參考類型比如 string, structs, mapping, 還有 arrays都需要。

3. 存取權限：這邊的 eatHamburgers 是 public，代表可以直接從 abi 呼叫這個 function

特別注意的是，習慣上的命名規則會把傳入參數以底線當作開頭。

ZombieFactory 需要加入一個 public function 名稱叫作 createZombie。 createZombie 需要兩個參數一個是字串 _name，一個是 uint _dna

更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
    Zombie[] public zombies;
    
    function createZombie(string memory _name, uint _dna) public {
        
    }
}
```

#### 使用 Struct and Arrays

在 solidity 中，

產生 struct 的語法如下:

```solidity=
struct Person {
    uint age;
    string name;
}
// 產生一個新的 Person
Person satoshi = Person(20, "Satoshi");
```
array 可以透過 array.push 新增元素到 array 之內

舉例如下
```solidity=
Person[] public people;
people.push(Person(20, "Satoshi"));
```

#### 加入 createZombie 邏輯

createZombie 會產生一個新的 Zombie，並且把這個 Zombie 加入 zombies array。

更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
    Zombie[] public zombies;
    
    function createZombie(string memory _name, uint _dna) public {
        zombies.push(Zombie(_name, _dna));     
    }
}
```

#### public/ private function

solidity 對於 function, 有 public 與 private 權限。

在 solidity ， function 預設都是 public。代表所有人都可以透過 abi 來呼叫 function 。

然而這樣會很容易把一些能夠修改資料的邏輯給透露給所有人，並非是一種安全的設計。

因此比較好的方式，把 function 預設設定為 private 限定只有 contract 物件可以呼叫。

只把一些需要外部互動的部份給設定成 public。

如下:
```solidity=
uint[] numbers;

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

通常習慣把 private 函式命名以底現作為開頭。

#### 更新 createZombie 為 private

更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
    Zombie[] public zombies;
    
    function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));     
    }
}
```

#### function 回傳值與修飾子

##### function 回傳值
讓 function 具有回傳值的語法如下:

```solidity=
string greeting = "What's up dog";

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

##### function 修飾子

1. view: 代表該 function 只會讀取參考值，並不去做修改。

如下：
```solidity=
string greeting = "What's up dog";

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

2. pure: 代表該 function 只做純量運算，不會去存取任何參考值。

如下:

```solidity=
function _multiply(uint _a, uint _b) private pure returns (uint) {
    return _a * _b;
}
```
#### 建立 _generateRandomDna function

每個 Zombie 具有一個特徵值 dna 

ZombieFactory 會需要一個產生隨機數的 function 來處理這個功能。

1. 建立一個 private function 名稱設定為 _generateRandomDna。_generateRandomDna 具有一個字串參數 _str，並且具有一個 uint 回傳值。

2. _generateRandomDna 不能修改 Contract 其他值，需要加入 view 修飾子。

更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
    Zombie[] public zombies;
    
    function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));     
    }
    
    function _generateRandomDna(string memory _str) private view returns(uint) {
        
    }
}
```

#### Keccak256 與型別轉換

##### Keccak256 

Ethereum 內建 keccak256 函式是一個用來做雜湊值的函數，把輸入資料對應到 256-bit 的 16進位數值。

輸入必須是 bytes，所以需要把輸入字串透過 abi.encodedPacked轉換成 byte。如下：

```solidity=
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256(abi.encodePacked("aaaab"));
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256(abi.encodePacked("aaaac"));
```

##### 型別轉換

型別轉換是用來轉換兩種不同型別，語法如下
```solidity=
uint8 a = 5;
uint b = 6;
// throws an error because a * b returns a uint, not uint8:
uint8 c = a * b;
// we have to typecast b as a uint8 to make it work:
uint8 c = a * uint8(b);
```

#### 更新 _generateRandomDna

1. 透過 abi.encodePacked(_str) 把輸入 string 產生 256 bit hexicimal string 再透過行別轉換成 uint 並且存入一個 uint 變數 rand
2. 把這個 rand % dnaModulus 在回傳

更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
    Zombie[] public zombies;
    
    function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));     
    }
    
    function _generateRandomDna(string memory _str) private view returns(uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }
}
```

#### 新增 createRandomZombie

1. 建立一個 public function，設定名稱叫作 createRandomZombie 。 createRandomZombie 需要一個字串參數 _name。
2. 把 _generateRandomDna 的值存在一個 uint 變數 randDna。
3. 把 _name 跟 randDna 傳入 _createZombie 函數。

更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
    Zombie[] public zombies;
    
    function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));     
    }
    
    function _generateRandomDna(string memory _str) private view returns(uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }
    
    function createRandomZombie(string memory _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randomDna);
    }
}
```

#### Events

Events 是一種讓 Contract 來根據 blockchain 資料變動來與前端應用互動的方式，透過這種方式可以監聽一些特定的變動。

如下：

```solidity=
// declare the event
event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public returns (uint) {
  uint result = _x + _y;
  // fire an event to let the app know the function was called:
  emit IntegersAdded(_x, _y, result);
  return result;
}
```

前端應用就可以透過以下語法來監聽:

```javascript=
YourContract.IntegersAdded(function(error, result) {
  // do something with result
})
```

#### 新增 NewZombie Event

1. 建立一個 NewZombie Event，並且傳遞資料 zombieId(uint), name(string), 還有 dna(uint)。
2. 新增驅動 NewZomie 到 _createZombie 邏輯裡，當新的 Zombie 物件加入 zombie 之後。
3. 需要透過存儲 array.push 的回傳值到一個 uint 變數 id 用來當作 NewZombie 的參數。

更新 ZombieFactory 如下

```solidity=
pramga solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    event NewZombie(uint zombieId, string name, uint dna);
    uint dnaDigits = 16;
    uint dnaModulus = dnaDigits**16;
    
    struct Zombie {
        string name;
        uint dna;
    }
    Zombie[] public zombies;
    
    function _createZombie(string memory _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        emit NewZombie(id, _name, _dna)
    }
    
    function _generateRandomDna(string memory _str) private view returns(uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }
    
    function createRandomZombie(string memory _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randomDna);
    }
}
```

到這裡，完整的 ZombieFactory 邏輯完成了

當把整個 ZombieFactory Contract Deploy 到已太鏈上時，

我們可以透過產生出來的 abi 使用 web3.js 從前端去呼叫 contract 來產生隨機 Zombie。

