【構建以太坊Dapp】-1-創建第一個智能合約

1、運行以太坊本地網絡

Hardhat 簡介

Hardhat,是一個可以在本地環境中輕鬆啓動一個以太坊網絡的工具。它會給我們用於測試的假的以太幣和假的的測試賬戶。它就像服務器,只不過這個“服務器”是區塊鏈。它還可以讓我們輕鬆編寫智能合約,並在本地以太坊網絡中測試。

安裝

我們將首先創建一個項目,並使用 npm 安裝 hardhat:

mkdir my-wave-portal
cd my-wave-portal
npm init -y
npm install --save-dev hardhat@latest

創建 hardhat 項目

npx hardhat

如下圖,選擇選項 Create a JavaScript project,剩下一路按 enter 鍵即可:

可以看到提示我們安裝hardhat-wafflehardhat-ethers,這些是我們後面會用到的包:

npm install --save-dev hardhat@^2.12.2 @nomicfoundation/hardhat-toolbox@^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

輸出如下:

可以看到揮手的錢包地址等於部署合約的地址。這是我在對自己揮手。我們

  1. 調用 wave 函數
  2. 更改狀態變量 totalWaves
  3. 讀取狀態變量最新值

這幾乎是大多數智能合約的基礎,讀取函數、編寫函數、並改變狀態變量。很快,我們將能從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 時,它實際上是:

  1. 創建一個新的本地以太坊網絡,
  2. 部署合約,
  3. 當腳本運行結束時,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

我們將看到類似的如下輸出:

我們部署了合約,我們也在區塊鏈上有了合約的地址。我們的前端網站將需要它,以便知道在區塊鏈上的何處查找合約。 (想象一下,如果必須在整個區塊鏈中搜索我們的合約。那將是多麼糟糕)

在本地以太坊網絡保持活躍的終端窗口中,我們會看到一些新東西,如下所示:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章