iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
Web 3

從 區塊鏈 到 去中心化應用程式(DApp)系列 第 21

智能合約開發: Hardhat 智能合約開發工具

  • 分享至 

  • xImage
  •  

Hardhat 智能合約開發工具

首先為何選擇 Hardhat ?
強大的測試框架、更好的性能

Hardhat 工作模式:

  • 合約撰寫 -> solidity
  • 部署與測試 -> JavaScript/TypeScript

而下文就依以下重點進行敘述

  1. Hardhat 基礎設定與使用
  2. 基礎合約範例的介紹
  3. 合約的部署、測試與互動

Hardhat 基礎設定與使用

1. 下載與初始化專案

Hardhat 官方網站: https://hardhat.org/

(戴著 Solidity圖示安全帽的工人 超可愛 哈哈哈)

首先透過官方說明方式
透過 npm 下載 hardhat 並且 初始化專案

npm init
npm install --save-dev hardhat
npx hardhat init

2. 初始化專案的選擇

根據先前指令 會獲得上述畫面
當然選擇 TypeScript (畢竟這是趨勢嘛!!!)

最後完整畫面如下:

3. 專案架構

// Project architecture
├── contracts
│   ├── Lock.sol
│
├── scripts
│   ├── deploy.ts
│
├── test
│   ├── Lock.ts
│
├── hardhat.config.ts
資料夾或檔案 作用
contracts 撰寫合約的位置
scripts 部署或是互動的腳本
test 測試腳本的位置
hardhat.config.ts 相關參數的設定檔

4. 主要功能

# 進行合約測試 (是否回報 GAS 花費)
npx hardhat test
REPORT_GAS=true npx hardhat test

# 建立 Hardhat 測試區塊鏈網路
npx hardhat node

# 執行腳本 部署合約
npx hardhat run scripts/deploy.ts
備註:
    npx hardhat node 不就能直接建立測試網路嗎?

    是的沒錯! 功能確實與 Ganache 重疊。
    但是 Ganache 提供視覺化的畫面 但是 Hardhat 僅提供文字介面,
    因此採用Ganache 加上 Hardhat 進行開發 方能以結合兩種優勢。

基礎合約範例的介紹

// ./contracts/Lock.sol
pragma solidity ^0.8.9;

contract Lock {
    uint public unlockTime;
    address payable public owner;

    event Withdrawal(uint amount, uint when);

    constructor(uint _unlockTime) payable {
        require(
            block.timestamp < _unlockTime,
            "Unlock time should be in the future"
        );

        unlockTime = _unlockTime;
        owner = payable(msg.sender);
    }

    function withdraw() public {
        
        require(block.timestamp >= unlockTime, "You can't withdraw yet");
        require(msg.sender == owner, "You aren't the owner");

        emit Withdrawal(address(this).balance, block.timestamp);

        owner.transfer(address(this).balance);
    }
}
  • 合約作用
    透過建立時,部署者可以把錢打進這合約並同時設定解鎖時間。
    然後可以透過合約 withdraw 功能 把先前的錢提領出來。

  • 合約基礎設定

pragma solidity ^0.8.9;
contract Lock {
    
}
程式碼內容 相對作用
pragma 合約使用的 Solidity 版本
contract 合約名稱(Lock)
  • 變數
uint public unlockTime;
address payable public owner;
程式碼內容 相對作用
unlockTime 合約解鎖的時間
owner 合約擁有者
  • 事件
event Withdrawal(uint amount, uint when);

function withdraw() public {
        emit Withdrawal(address(this).balance, block.timestamp);
}
程式碼內容 相對作用
event 事件的定義 內容包括 金額與時間
emit 新增事件的方法
  • 建構子
constructor(uint _unlockTime) payable {
        require(
            block.timestamp < _unlockTime,
            "Unlock time should be in the future"
        );

        unlockTime = _unlockTime;
        owner = payable(msg.sender);
}
程式碼內容 相對作用
constructor 建構子的關鍵字
payable 接受金額進入
require 必須先通過此處驗證 否則會被退回

透過 msg 可以拿到此次與合約互動的使用者資訊
並將其設為 payable 未來可以把金額轉回給 擁有者

  • 函式(提款)
function withdraw() public {
        
        require(block.timestamp >= unlockTime, "You can't withdraw yet");
        require(msg.sender == owner, "You aren't the owner");

        emit Withdrawal(address(this).balance, block.timestamp);

        owner.transfer(address(this).balance);
}
程式碼內容 相對作用
require 必須先通過此處驗證 否則會被退回
emit 新增事件的方法
owner.transfer 轉移金額

合約的部署、測試與互動

連接 Ganache 網路

// hardhat.config.ts
const config: HardhatUserConfig = {
  solidity: "0.8.19",
  networks: {
    ganache: {
      url: "http://localhost:7545",
    },
  },
};

export default config;

部署合約

(確保當前 Ganache 程序仍在啟動 以方便部署合約上去)

1. 修改部署程序

// scripts/deploy.ts 修改
import { ethers } from "hardhat";

async function main() {
  const currentTimestampInSeconds = Math.round(Date.now() / 1000);
  const unlockTime = currentTimestampInSeconds + 60;

  //這裡修改成 20 ETH 比較有感覺
  const lockedAmount = ethers.parseEther("20");

  const lock = await ethers.deployContract("Lock", [unlockTime], {
    value: lockedAmount,
  });

  await lock.waitForDeployment();

  console.log(
    `Lock with ${ethers.formatEther(
      lockedAmount
    )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}`
  );
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

2. 觀察原先帳戶資訊

3. 執行部署

npx hardhat run scripts/deploy.ts --network ganache

輸出結果如下:

包含合約鎖定金額、解鎖時間、部署合約位置

4. 觀察合約部署後帳戶資訊

確實第一筆帳戶又少了 20 ETH

5. 進行測試

npx hardhat test

輸出結果如下:

6. 提領金額

// scripts/withdraw_in_lock_contract.ts
import {ethers} from "hardhat";
import { Contract } from "ethers";

async function main() {
    const abi = require("../artifacts/contracts/Lock.sol/Lock.json").abi;

    const [deployer,user_with_index1] = await ethers.getSigners();

    console.log("User's Address: ",user_with_index1.address)

    const contractAddress = '0x3dA256b2074dB294B88535402E44652325616822';

    const contract = new Contract(contractAddress, abi, user_with_index1);

    await contract.withdraw().then((result)=>{
        console.log("Result: ", result);
    }).catch((e)=>{
        console.log("Error: ",e)
    })
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

合約位置上 貼上剛才 建立合約的位置

會發現試圖透過第二個帳戶去提領剛才的合約金額
發生錯誤 提領不出來!!!

備註:
    ABI文件 這是在合約部署後會生成的文件
    這文件代表了 合約的輸入輸出等等資訊 並且會在部署完成後自動生成

修改成正確帳戶 由 deployer 進行提領

// 進行修正
console.log("User's Address: ",deployer.address)

const contract = new Contract(contractAddress, abi, deployer);

成功提領!!!

備註:
    可能會發現無法提領出來
    那是因為 Ganache 的區塊時間 如果沒有新區塊產生會更新
    因此要先產一筆交易去更新 Ganache 區塊時間 
    (可以透過 MetaTask 轉給自己金額)

7. 重新觀察帳戶(Ganache)

確實沒錯,金額領回來了第一個帳戶!!!

8. 獲取事件

// scripts/view_events_lock_contract.ts
import {ethers} from "hardhat";

async function main() {
    const [deployer]=await ethers.getSigners();

    const contractAddress = '0x3dA256b2074dB294B88535402E44652325616822';
    const contractABI = require("../artifacts/contracts/Lock.sol/Lock.json").abi;

    const contract = new ethers.Contract(contractAddress, contractABI, deployer);

    const filter = contract.filters.Withdrawal();
    const events = await contract.queryFilter(filter);

    console.log(events)
}

main().then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

輸出結果如下:

沒錯 最後參數也寫下 提款金額(20 ETH) 與 提款時間

備註:
    1 ETH 等於 10^9 Wei(最小單位)
    1 nano-ETH(nETH)等於 10^9 Wei
    
    因此 20000000000000000000n = 20 ETH

結言

GitHub 專案位置: https://github.com/weiawesome/hardhat_tutorial

關於 Hardhat 的安裝與使用 應該算是蠻清晰了

基本上現在已經能理解如何發布合約、合約互動等等

而合約有點像是個相當公正(一板一眼)的中間機構
可以把錢給它 它可以根據程序去做處理(完全沒人情)

它無法主動拿走其他帳戶的錢
但它可以把自己的錢做運用(比如把合約的錢轉回給使用者)

透過這篇文章能夠理解

  1. 如何下載 Hardhat
  2. 透過 Hardhat 初始化專案
  3. 透過 Hardhat 部署合約
  4. 透過 Hardhat 測試合約
  5. 透過 Hardhat 與合約互動

下回預告

今天真的實際去部署了一份合約
甚至還與合約互動了~~~

部署合約、提領金額、觀察事件...

智能合約 具有相當多特性
包括 自動化、公開性、公正 等等

當然想透過這些特性 來撰寫一個合約吧

下回 "智能合約開發: 建立合約-公平、公正的投票"


上一篇
智能合約開發: 測試網路(Ganache)與虛擬錢包(MetaTask)
下一篇
智能合約開發: 建立合約-公平、公正的投票
系列文
從 區塊鏈 到 去中心化應用程式(DApp)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言