1、運行以太坊本地網絡
Hardhat 簡介
Hardhat,是一個可以在本地環境中輕鬆啓動一個以太坊網絡的工具。它會給我們用於測試的假的以太幣和假的的測試賬戶。它就像服務器,只不過這個“服務器”是區塊鏈。它還可以讓我們輕鬆編寫智能合約,並在本地以太坊網絡中測試。
安裝
我們將首先創建一個項目,並使用 npm 安裝 hardhat:
mkdir my-wave-portal
cd my-wave-portal
npm init -y
npm install --save-dev [email protected]
創建 hardhat 項目
npx hardhat
如下圖,選擇選項 Create a JavaScript project
,剩下一路按 enter 鍵即可:
可以看到提示我們安裝hardhat-waffle
和hardhat-ethers
,這些是我們後面會用到的包:
npm install --save-dev [email protected]^2.12.2 @nomicfoundation/[email protected]^2.0.0
除此之外,我們還要安裝如下我們也需要的包:
npm install --save-dev chai @nomiclabs/hardhat-ethers ethers @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-chai-matchers
最後,運行 npx hardhat node
,這應該會打印出一堆賬戶,看起來像這樣:
這些是 Hardhat爲 我們生成的以太坊地址,用於模擬區塊鏈上的真實用戶,便於我們測試。
編譯
如果一切正常,我們可以運行如下命令,最合約進行編譯:
npx hardhat compile
然後運行測試:
npx hardhat test
我們應該能看到如下的樣子:
清理
上面測試的其實是項目一開始創建時生成的一些代碼,那些代碼對我們用處不大,所以應該把它們刪除掉:
- 刪除 test 目錄下的 Lock.js,
- 刪除 scripts 目錄下的 deploy.js,
- 刪除 contracts 目錄下的 Lock.sol。
2、編寫第一個智能合約
在 contracts
目錄下創建一個文件並將其命名爲 WavePortal.sol
,文件代碼如下:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import "hardhat/console.sol";
contract WavePortal {
constructor() {
console.log("Yoho, I am a contract and I am smart");
}
}
解釋:
-
// SPDX-License-Identifier: UNLICENSED
:被稱爲 “SPDX-License-Identifier”,是必須的,便然你的編輯器會一直有警告的提示。 -
pragma solidity 0.8.17;
:希望合約使用的 Solidity 編譯器的版本。基本意思是 “當運行合約的時候,我只想要使用 0.8.17 版本的 Solidity 編譯器,低一點都不行。” 注意,要確保編譯器的版本在hardhat.config.js
中是一樣的。 -
import "hardhat/console.sol";
:在合約中做了一些控制檯日誌。 -
contract WavePortal
:該智能合約的名字,有點像其他語言中的 "類"。 -
constructor
:構造函數,一旦我們第一次初始化這個合約,這個結構函數就會運行並打印出Yoho, I am a contract and I am smart
。
3、本地編譯、運行智能合約
上面我們寫了一份合約,現在我們來在本地編譯、運行它。
構建運行合約腳本
進入 scripts
目錄並創建一個名爲 run.js
的文件,要測試智能合約,我們必須正確地做很多事情。比如:編譯,部署,然後執行。編寫腳本將使我們非常容易地快速迭代我們的合約。
run.js
代碼如下:
const main = async () => {
const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
console.log("Contract deployed to: ", waveContract.address);
}
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
}
釋義:
-
const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
:實際編譯我們的合約並在artifacts
目錄下生成我們需要使用我們的合約的必要文件。 -
const waveContract = await waveContractFactory.deploy();
:Hardhat 將爲我們創建一個本地以太坊網絡,但只是爲了這個合約。然後,在腳本完成後,它會破壞該本地網絡。因此,每次運行合約時,它都會是一個全新的區塊鏈。這有點像每次都刷新你的本地服務器,所以你總是從一個乾淨的區塊鏈開始,這樣可以輕鬆調試錯誤。 -
await waveContract.deployed();
:等到我們的合約正式部署到我們的本地區塊鏈,合約的constructor
g構造方法中的代碼將被運行(智能合約初始化)。 -
console.log("Contract deployed to:", waveContract.address);
:waveContract.address 是合約的地址。這個合約地址挺重要的,只有通過這個合約地址,我們才能調用合約中的代碼。
運行腳本
npx hardhat run scripts/run.js
你應該能看到在本地控制檯上,打印出合約的地址來,如下所示:
Hardhat & HRE
上面代碼中,你應該注意到使用了 hre.ethers
,而且它不用從任何地方導入 hre
,這是什麼情況呢?
直接從 Hardhat 文檔,我們會注意到有如下這一點:
Hardhat 運行時環境,或簡稱 hre,是一個包含 Hardhat 在運行任務、測試或腳本時公開的所有功能的對象。實際上,Hardhat 是 hre。
所以,每次運行以 npx hardhat
開頭的終端命令時,都會使用代碼中指定的 hardhat.config.js
動態構建這個 hre 對象。這意味着可以不必實際對文件進行某種導入,例如:
const hre = require("hardhat");
所以,我們會在代碼中看到很多 hre
,但從未在任何地方導入。可以查看 Hardhat 文檔 以瞭解更多信息。
4、智能合約中數據存儲
我們希望能夠讓某人向我們揮手wave,然後存儲該揮手。所以,我們首先需要的是一個他們可以點擊向我們揮手的功能。
區塊鏈 = 把它想象成一個雲提供商,有點像騰訊雲或阿里雲,但它不歸任何人所有。它由來自世界各地的礦機的計算能力運行。通常這些人被稱爲礦工,我們付錢給他們來運行我們的代碼
智能合約 = 有點像我們服務器的代碼,具有人們可以調用的不同功能。
接下來,我們更新合約 WavePortal.sol
的內容如下:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import "hardhat/console.sol";
contract WavePortal {
uint256 totalWaves;
constructor() {
console.log("Yoho, I am a contract and I am smart");
}
function wave() public {
totalWaves += 1;
console.log("%s has waved", msg.sender);
}
function getTotalWaves() public view returns (uint256) {
console.log("We have %d total waves!", totalWaves);
return totalWaves;
}
}
以上就是 Solidity 中編寫函數的方式。我們添加了一個初始化爲 0 的 totalWaves
變量。這個變量很特別,它被稱爲“狀態變量”,因爲它永遠存儲在合約存儲中。
msg.sender
,這是調用該函數的錢包地,它就像內置身份驗證。它讓我們確切地知道是誰調用了這個函數,所以爲了調用智能合約函數,你需要連接一個有效的錢包。
我們可以編寫只有特定錢包地址才能命中的函數。例如,我們可以更改此功能,只允許我們定義的白名單地址纔可以發送 wave。
更新腳本
基本上,當我們將合約部署到區塊鏈時(運行 waveContractFactory.deploy() 時),我們的函數就可以在區塊鏈上被調用,因爲我們在函數上使用了特殊的 public
關鍵字。可以把它想象成一個公共 API 端點。
所以,更新 run.js
腳本如下:
const main = async () => {
const [owner, randomPerson ] = await hre.ethers.getSigers();
const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
console.log("Contract deployed to: ", waveContract.address);
console.log("Contract deployed by: ", owner.address);
let waveCount;
waveCount = await waveContract.getTotalWaves();
let waveTxn = await waveContract.wave();
await waveTxn.wait();
waveCount = await waveContract.getTotalWaves();
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
釋義:
-
const [owner, randomPerson ] = await hre.ethers.getSigers();
:爲了將某些東西部署到區塊鏈,我們需要有一個錢包地址。Hardhat 在後臺爲我們做了這件事,在這裏我們抓取了合約所有者的錢包地址(owner),也抓取了一個隨機的錢包地址(randomPerson)。 - 爲了查看部署合約的地址,添加了一條打印:
console.log("Contract deployed by: ", owner.address);
。 - 接下來,手動調用合約中的函數。先調用獲取揮手次數的函數(getTotalWaves),然後調用揮手函數(wave),最後在調用獲取揮手次數的函數(getTotalWaves),這樣我們就可以觀察到
waveCount
是否發生了變化。
運行腳本
npx hardhat run scripts/run.js
輸出如下:
可以看到揮手的錢包地址等於部署合約的地址。這是我在對自己揮手。我們
- 調用 wave 函數
- 更改狀態變量 totalWaves
- 讀取狀態變量最新值
這幾乎是大多數智能合約的基礎,讀取函數、編寫函數、並改變狀態變量。很快,我們將能從vue、react等前端中調用這些函數。
測試其他用戶
下面模擬其他用戶向我們揮手,run.js
代碼更新如下:
const main = async () => {
const [owner, randomPerson ] = await hre.ethers.getSigners();
const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
console.log("Contract deployed to: ", waveContract.address);
console.log("Contract deployed by: ", owner.address);
let waveCount;
waveCount = await waveContract.getTotalWaves();
let waveTxn = await waveContract.wave();
await waveTxn.wait();
waveCount = await waveContract.getTotalWaves();
// 模擬其他用戶連接錢包向我們揮手
waveTxn = waveContract.connect(randomPerson).wave();
await waveTxn.wait();
waveCount = await waveContract.getTotalWaves();
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
運行腳本後之後,如下入所示:
5、編寫腳本並在本地部署
啓動本地網絡
運行 scripts/run.js
不是已經部署到本地網絡了嗎?
這只是一部分,請記住,當在運行 scripts/run.js
時,它實際上是:
- 創建一個新的本地以太坊網絡,
- 部署合約,
- 當腳本運行結束時,Hardhat 將自動銷燬該本地網絡。
所以,我們需要一種方法來保留本地網絡,就像本地服務器,我們要讓它保持在運行狀態,這個我們才能與它交互。
回到項目根目錄,打開新的終端窗口,在窗口中輸入以下命令,以啓動本地節點:
npx hardhat node
這樣我們就啓動了一個保持活動的本地以太坊網絡,而且,如你所見,Hardhat 爲我們提供了 20 個賬戶,並給了每個賬戶 10000 ETH。(只是本地的測試幣哦)
創建部署腳本
現在,是一個空的區塊鏈,還沒有區塊。
我們現在要創建一個新區快並在其上獲取我們的智能合約。
在 scripts
文件夾下,創建一個名爲 deploy.js
的文件。代碼如下,看起來與 run.js
非常相似。
const main = async () => {
const [deployer] = await hre.ethers.getSigners();
const accountBalance = await deployer.getBalance();
console.log("Deploying contracts with account: ", deployer.address);
console.log("Account balance: ", accountBalance.toString());
const Token = await hre.ethers.getContractFactory("WavePortal");
const portal = await Token.deploy();
await portal.deployed();
console.log("WavePortal address: ", portal.address);
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.error(error);
process.exit(1);
}
};
runMain();
部署腳本
保持上面啓動本地網絡的窗口不要關閉,在開啓一個新的終端窗口,輸入以下運行本地部署腳本命令:
npx hardhat run scripts/deploy.js --network localhost
我們將看到類似的如下輸出:
我們部署了合約,我們也在區塊鏈上有了合約的地址。我們的前端網站將需要它,以便知道在區塊鏈上的何處查找合約。 (想象一下,如果必須在整個區塊鏈中搜索我們的合約。那將是多麼糟糕)
在本地以太坊網絡保持活躍的終端窗口中,我們會看到一些新東西,如下所示: