## 有趣的以太坊與Solidity ### 鍾孟儒 --- 今天會講些什麼呢? 1. 介紹EVM與Solidity 2. Solidity架構與語法 3. 部屬一個簡單合約 --- 讓我們先簡單介紹一下以太坊 ![](https://i.imgur.com/HSlGq90.png) --- 一言以蔽之 以太坊是`世界計算機` --- 一個可編程化的區塊鏈平台 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 / &nbsp;&nbsp; \\ B&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;C \ &nbsp;&nbsp; / 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) ![](https://i.imgur.com/sBtpR4u.png) 複製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}]"}
    119 views