首先為何選擇 Hardhat ?
強大的測試框架、更好的性能
Hardhat 工作模式:
而下文就依以下重點進行敘述
Hardhat 官方網站: https://hardhat.org/
(戴著 Solidity圖示安全帽的工人 超可愛 哈哈哈)
首先透過官方說明方式
透過 npm 下載 hardhat 並且 初始化專案
npm init
npm install --save-dev hardhat
npx hardhat init
根據先前指令 會獲得上述畫面
當然選擇 TypeScript (畢竟這是趨勢嘛!!!)
最後完整畫面如下:
// Project architecture
├── contracts
│ ├── Lock.sol
│
├── scripts
│ ├── deploy.ts
│
├── test
│ ├── Lock.ts
│
├── hardhat.config.ts
資料夾或檔案 | 作用 |
---|---|
contracts | 撰寫合約的位置 |
scripts | 部署或是互動的腳本 |
test | 測試腳本的位置 |
hardhat.config.ts | 相關參數的設定檔 |
# 進行合約測試 (是否回報 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 | 轉移金額 |
// hardhat.config.ts
const config: HardhatUserConfig = {
solidity: "0.8.19",
networks: {
ganache: {
url: "http://localhost:7545",
},
},
};
export default config;
(確保當前 Ganache 程序仍在啟動 以方便部署合約上去)
// 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;
});
npx hardhat run scripts/deploy.ts --network ganache
輸出結果如下:
包含合約鎖定金額、解鎖時間、部署合約位置
確實第一筆帳戶又少了 20 ETH
npx hardhat test
輸出結果如下:
// 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 轉給自己金額)
確實沒錯,金額領回來了第一個帳戶!!!
// 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 的安裝與使用 應該算是蠻清晰了
基本上現在已經能理解如何發布合約、合約互動等等
而合約有點像是個相當公正(一板一眼)的中間機構
可以把錢給它 它可以根據程序去做處理(完全沒人情)
它無法主動拿走其他帳戶的錢
但它可以把自己的錢做運用(比如把合約的錢轉回給使用者)
透過這篇文章能夠理解
今天真的實際去部署了一份合約
甚至還與合約互動了~~~
部署合約、提領金額、觀察事件...
智能合約 具有相當多特性
包括 自動化、公開性、公正 等等
當然想透過這些特性 來撰寫一個合約吧