## 有趣的以太坊與Solidity
### 鍾孟儒
---
今天會講些什麼呢?
1. 介紹EVM與Solidity
2. Solidity架構與語法
3. 部屬一個簡單合約
---
讓我們先簡單介紹一下以太坊

---
一言以蔽之
以太坊是`世界計算機`
---
一個可編程化的區塊鏈平台
1. 擁有區塊鏈分散式、不可竄改的特性
2. 是`圖靈完備`的,
可以使用高級語言編寫各種應用程式
3. ~~投資理財工具(非投資建議(真的不是))~~
---
### 而Solidity又是什麼呢
---
Solidity
就是用來將合約運行在EVM上的高級程式語言
---
???
EVM又是什麼
---
`ETHEREUM VIRTUAL MACHINE (EVM)`
以太坊虛擬機
是智能合約的運行環境
`以太坊 <- EVM <- Solidity`
只要是支援EVM的區塊鏈
都可以部屬Solidity寫的程式
(不只是以太坊)
---
那...我們是如何讓EVM運行我們的合約呢
---
以太坊使用`gas`的概念來運行合約
`gas`的單位就是以太幣
使用者運行合約函式需要支付`gas`
EVM依照各種量化規則消耗`gas`
---
越複雜的合約(行數超多)
或是使用到如`全域狀態修改`等功能的合約
使用的gas也就越多
如何減少gas消耗是優化合約很重要的一環
---
一份Solidity文件是怎麼組成的呢
---
讓我們先從最外面開始
```solidity
// SPDX-License-Identifier: UNLICENSED 👈用哪種License
```
```solidity
pragma solidity ^0.8.0; 👈讓compiler知道用哪個版本
```
```solidity
import "hardhat/console.sol";
import "contracts/BContract.sol";
👆用到哪個外部文件,等於python的import
```
```solidity
contract AContract is BContract { 👈合約宣告,等於C#的class
...
}
```
---
Solidity也有些我們常見的型別
```solidity
struct Book {
string Name;
uint256 UID;
}
enum week_days {
Monday, Tuesday, Wednesday...
}
interface IPet {
function SetMaster(address master) public;
}
```
---
再來看看contract內部
```solidity
string[] internal words;
mapping (address => uint) public UsersTalkTimes;
```
```solidity
constructor() payable { 👈建構子
console.log("I am a smart contract by ", msg.sender,
"with ", msg.value
);}
```
```solidity
function Say(string memory _message) public returns (string memory) {
words.push(_message);
UsersTalkTimes[msg.sender] += 1;
return "Roger that.";
}
```
```solidity
function MyTalkTimes() public view returns (uint) {
return UsersTalkTimes[msg.sender];
}
```
---
## Function
function `[Name]` (`[Parameters]`) `[Visibility]` `[Modifiers]` returns `[Returns]` {...}
---
## Function Visibility
(全域變數亦同)
- `external`: 僅供外部函式呼叫
- `internal`: 僅供內部及繼承者呼叫
- `public`、`private`: 等於正常理解
---
## Function Modifier
- `pure`: 完全工具函式,不訪問任何數據
- `view`: 只能夠讀取數據
- `payable`: 讓函式可以接收以太幣
- `virtual`、`override`: 等於正常理解
- `custom`: 自訂modifier
---
## Custom Modifier
```solidity
address owner;
construct() {
owner = msg.sender;
}
```
```solidity
modifier onlyOwner() {
require(msg.sender == owner); //必須滿足條件才會繼續
_; // modifire專用,回到原本執行的函示
}
```
```solidity
modifier checkOwner(address _addr) {
require(_addr == owner, "Address is not the owner.");
_;
}
```
```solidity
function DoSomethingByOwner() public onlyOwner {...}
function DoSomethingByOwner() public checkOwner(msg.sender) {...}
```
---
## `require` vs `assert` vs `revert`
```solidity
require(some_condition, ["error_msg"]);
assert(some_condition, ["error_msg"]);
revert(["error_msg"]);
```
當觸發時,還原所有狀態變化
- `require`: 失敗時歸還剩餘gas
- `assert`: 失敗時消耗所有剩餘gas
- `revert`: 沒有判斷條件的require
---
## 讓我們來談談儲存
`儲存`在EVM分為兩種
1. 永久儲存,廣播儲存在區塊鏈上 👈昂貴
2. 內存使用,存在當前客戶端內存上 👈便宜
這裡的昂貴不只是計算機上的昂貴而是真的`昂貴`
---
```solidity
function Say(string memory _message)
public view returns (string memory)
```
---
- `memory`: 變數以內存的值傳進來,修改不牽涉鏈上數據,可看作pass by value
- `storage`: 變數以鏈上數據地址傳入,修改值即修改鏈上數據,可看作pass by reference
- `calldata`: 類似於memory, 但不可被修改
---
How about 繼承?
```solidity
contract A {
function foo() public virtual returns (string memory) {
console.log("A");
}
}
contract B is A {
function foo() public
virtual override returns (string memory) {
console.log("B");
}
}
```
---
Solidity是允許多重繼承的
所以也會有鑽石繼承問題
A
/ \\
B C
\ /
D
---
```solidity
contract C is A, B {
function foo() public override(A, B){
A.foo();
B.foo();
super.foo();
// super.foo() 會印出 "B"
// 因為B是繼承關係中最右邊的母體
}
}
```
---
讓我們來實際操作一下試試
---
建立、測試一個EVM專案, 我們需要
1. `Hardhat` - 一個基於NPM的開發環境
2. `Alchemy` - 協助我們部屬合約的節點服務
3. `Metamask` - 與部屬合約交互的瀏覽器插件
*(創建錢包帳戶、與前端交互)*
---
## Hardhat
```powershell
npm init -y 👈創建一個npm專案
npm install --save-dev hardhat 👈安裝hardhat
npx hardhat 👈初始化Hardhat專案
```
---
測試合約
```powershell
npx hardhat run scripts/run.js 👈一次性測試合約
```
```powershell
npx hardhat node 👈創建一個本地測試環境
npx hardhat run scripts/deploy.js --network localhost
👆 部屬到本地測試環境
```
---
## 部屬合約到測試網
先上`alchemy.com`申請API KEY
(記得選Test net,例如Rinkeby)

複製HTTP那欄取得API URL
---
打開Chrome創建一個Metamask(小狐狸)錢包帳戶
並取得帳戶私鑰
---
修改`hardhat.config.js`
```javascript
require("@nomiclabs/hardhat-waffle");
module.exports = {
solidity: "0.8.0",
networks: {
rinkeby: {
url: "你的_ALCHEMY_API_URL",
accounts: ["小狐狸錢包帳戶私鑰"]
},
},
};
```
---
記得先取得一點測試用gas存入錢包
And run
```powershell
npx hardhat run scripts/deploy.js --network rinkeby
```
---
# 大功告成!
## 你部屬了第一個合約
---
接著只需要串上前端就能進行交互了!!
而那 又是另一個故事了
---
## Extra
還有些特別的,例如`Library`
```solidity
library StringLib {
function compare(string memory str1, string memory str2)
public pure returns (bool)
return keccak256(bytes(str1)) == keccak256(bytes(str2));
}
}
```
---
Library寫法與一般合約沒有太大的差異
但使用上還是有些異同
1. 沒有儲存的功能
2. 不能使用 payable 函式
3. 不能被繼承,但可以連接其他函式庫 (import)
4. 不能使用event
---
## OpenZeppelin
線上合約Library
開源、安全、安心
---
## 安裝
[OpenZeppelin官方教學](https://docs.openzeppelin.com/learn/developing-smart-contracts#using-openzeppelin-contracts)
```powershell
npm install @openzeppelin/contracts
```
---
## Reference
* [Solidity官方文檔](https://docs.soliditylang.org)
* [buildspace](https://app.buildspace.so): 各種Web3教程
* [cryptozombies](https://cryptozombies.io): 從遊戲中一步步學習
* [useweb3.xyz](https://www.useweb3.xyz/): 各種Web3資源入口
* [etherscan.io](https://etherscan.io): 查看以太坊鏈上各種資訊
* [rinkeby.etherscan.io](https://rinkeby.etherscan.io/): Rinkeby測試網鏈上資訊
{"metaMigratedAt":"2023-06-16T22:27:29.174Z","metaMigratedFrom":"YAML","title":"大功告成!","breaks":true,"contributors":"[{\"id\":\"a52d3b07-c3bd-4df4-a8c7-ec36ec9b5fcc\",\"add\":8364,\"del\":2570}]"}