以太坊智能合約開發入門實戰:從環境搭建到第一個部署合約

本文專為區塊鏈開發新手設計,提供以太坊智能合約開發的完整入門指南。我們從零開始,帶領讀者完成開發環境的搭建(Node.js、Hardhat、Foundry)、編寫第一個智能合約(ERC-20代幣)、部署到測試網絡,並通過實際案例講解智能合約的核心概念。本文涵蓋Solidity語言基礎、Hardhat配置、部署腳本編寫、測試用例設計等完整流程,是開發者入門以太坊智能合約開發的實用參考資料。

以太坊智能合約開發入門實戰:從環境搭建到第一個部署合約

概述

本文專為區塊鏈開發新手設計,提供以太坊智能合約開發的完整入門指南。我們將從零開始,帶領讀者完成開發環境的搭建、編寫第一個智能合約、部署到測試網絡,並通過實際案例講解智能合約的核心概念。本文適合具備基本程式設計經驗但尚未接觸區塊鏈開發的讀者,無需事先了解以太坊或Solidity語言。

智能合約是以太坊生態系統的核心,它是一種運行在區塊鏈上的自動執行程式。當預設條件被觸發時,合約會自動執行相關邏輯,無需任何中介機構的參與。這種「代碼即法律」的特點使得智能合約成為去中心化應用的基石。

本教程的目標是幫助讀者在本地機器上建立完整的開發環境,編寫並部署一個功能完整的ERC-20代幣合約,並理解智能合約開發的完整流程。通過完成本教程,讀者將具備獨立開發基本以太坊智能合約的能力。

前置技能要求:本教程假定讀者具備以下基本技能:熟悉至少一種程式語言(如JavaScript、Python、C++)、了解命令行基本操作、具有程式碼編輯器使用經驗。不需要任何區塊鏈或密碼學背景知識。


第一章:開發環境搭建

1.1 為什麼選擇這些工具

以太坊智能合約開發需要一套專門的工具鏈。本教程選擇的工具組合是目前業界最流行、文档最完善、社區支持最廣的方案。

Hardhat是以太坊智能合約開發的首選開發框架。它基於Node.js,提供了編譯、部署、測試、調試等完整功能。Hardhat的優勢在於:內置的本地區塊鏈網絡(Hardhat Network)讓部署變得快速簡單;詳盡的錯誤訊息幫助開發者快速定位問題;豐富的插件生態系統支持各種擴展功能。

Foundry是另一個流行的開發框架,使用Rust編寫,強調速度和安全性。Foundry的優勢在於:極快的測試執行速度、强大的Forge測試框架、便捷的Cast命令行工具。本教程以Hardhat為主,因為它對新手更友好,但會在適當時機介紹Foundry的相關功能。

Solidity是以太坊智能合約的官方編程語言,語法類似JavaScript,對新手比較友好。Solidity是一靜態類型語言,支援繼承、庫、 пользовательских類型等高級特性。2026年第一季度,Solidity的最新版本為0.8.28,引入了許多新特性包括更嚴格的類型檢查和更高效的位元操作。

1.2 安裝Node.js和開發工具

首先,我們需要在系統上安裝Node.js環境。Node.js是一個基於Chrome V8引擎的JavaScript運行時,是Hardhat等以太坊開發工具的基礎。

步驟一:安裝Node.js

打開終端,檢查系統是否已安裝Node.js:

node --version

如果顯示版本號(例如v20.11.0),表示已安裝Node.js。如果顯示「command not found」,則需要進行安裝。

在macOS或Linux上,可以使用nvm(Node Version Manager)安裝Node.js:

# 安裝nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# 重啟終端後,執行以下命令安裝Node.js LTS版本
nvm install 20

# 使用Node.js 20
nvm use 20

# 驗證安裝
node --version

在Windows上,可以直接從Node.js官網下載安裝程式,或使用Windows Subsystem for Linux(WSL)獲得更好的開發體驗。

步驟二:安裝Hardhat

創建一個新的項目目錄,並初始化npm項目:

# 創建項目目錄
mkdir my-eth-project
cd my-eth-project

# 初始化npm項目
npm init -y

接下來安裝Hardhat和其他必要的開發依賴:

# 安裝Hardhat
npm install --save-dev hardhat

# 安裝OpenZeppelin contracts(安全的智能合約庫)
npm install @openzeppelin/contracts

# 安裝 ethers.js(以太坊JavaScript庫)
npm install ethers

# 安裝 dotenv(環境變量管理)
npm install dotenv

步驟三:初始化Hardhat項目

使用Hardhat的交互式初始化向導創建項目結構:

npx hardhat init

系統會提示選擇項目類型,選擇「Create a JavaScript project」:

? What do you want to do? …
❯ Create a JavaScript project
  Create a TypeScript project
  Create a TypeScript project (with Viem)
  Create an empty Hardhat config

選擇後,Hardhat會自動創建項目結構並安裝必要的依賴。項目結構如下:

my-eth-project/
├── contracts/           # 智能合約原始碼
│   └── Lock.sol         # 示例合約
├── scripts/             # 部署腳本
│   └── deploy.js
├── test/                # 測試文件
│   └── Lock.js
├── hardhat.config.js    # Hardhat配置
├── package.json         # npm依賴
└── README.md

1.3 配置Hardhat

打開hardhat.config.js文件,配置網絡和編譯器選項:

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: "0.8.28",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    // 本地測試網絡
    localhost: {
      url: "http://127.0.0.1:8545"
    },
    // Sepolia測試網絡
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      chainId: 11155111
    },
    // 主網
    mainnet: {
      url: process.env.MAINNET_RPC_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      chainId: 1
    }
  },
  // Etherscan/BscScan API用於驗證合約
  etherscan: {
    apiKey: {
      sepolia: process.env.ETHERSCAN_API_KEY || ""
    }
  }
};

創建.env文件用於存儲敏感信息:

# .env文件示例
# 請複制此文件為.env並填入實際值

# 測試網絡RPC URL(從Alchemy或Infura獲取)
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY

# 錢包私鑰(不要分享!)
# 建議使用測試帳戶的私鑰
PRIVATE_KEY=0xyour_private_key_here

# Etherscan API Key(用於合約驗證)
ETHERSCAN_API_KEY=your_etherscan_api_key

安全警告:千萬不要將包含真實資金的錢包私鑰提交到版本控制系統。在實際項目中,應使用硬體錢包或錢包管理工具(如Hardhat WalletConnect)進行簽名。


第二章:編寫第一個智能合約

2.1 Solidity語言基礎

在編寫智能合約之前,我們需要了解Solidity語言的基本語法。Solidity是一種類JavaScript的靜態類型語言,主要用於編寫以太坊智能合約。

合約結構

Solidity合約類似於面向對象語言中的類。每個合約可以包含變量、函數、事件和修飾符。以下是一個簡單的合約結構:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

// 這是一個簡單的合約示例
contract SimpleStorage {
    // 狀態變量
    uint256 private storedData;
    
    // 事件 - 用於記錄區塊鏈上的重要操作
    event DataStored(uint256 value, address indexed sender);
    
    // 函數 - 設置值
    function set(uint256 x) public {
        storedData = x;
        emit DataStored(x, msg.sender);
    }
    
    // 函數 - 獲取值
    function get() public view returns (uint256) {
        return storedData;
    }
}

數據類型

Solidity支持多種基本數據類型:

類型說明示例
uint256無符號整數(256位)uint256 a = 100;
int256有符號整數int256 b = -50;
bool布爾值bool flag = true;
address以太坊地址address owner = 0x123...;
bytes32固定長度字節數組bytes32 hash = keccak256(...);
string動態字符串string name = "Ethereum";
mapping鍵值對映射mapping(address => uint256) balances;

訪問控制

Solidity提供多種訪問控制修飾符:

contract AccessControl {
    address public owner;
    
    // 構造函數 - 合約部署時執行
    constructor() {
        owner = msg.sender;
    }
    
    // 函數修飾符 - 添加前置條件檢查
    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }
    
    // 只有所有者可以調用的函數
    function adminOnlyFunction() public onlyOwner {
        // 僅管理員邏輯
    }
}

2.2 編寫ERC-20代幣合約

現在,我們來編寫一個完整的ERC-20代幣合約。ERC-20是以太坊上代幣的標準接口,定義了代幣轉移、餘額查詢、授權等基本功能。

什麼是ERC-20

ERC-20是以太坊上同質化代幣的技術標準。任何符合ERC-20標準的代幣都可以在支持以太坊的錢包和交易所中使用。ERC-20定義了以下必需函數和事件:

完整代碼

contracts目錄下創建新文件MyToken.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

/**
 * @dev 提供基本代幣功能的OpenZeppelin ERC20擴展
 */
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @dev 可燃燒、可鑒造的ERC20代幣合約
 * 
 * 這個合約實現了一個功能完整的代幣,包括:
 * - 基本轉帳功能
 * - 代幣燃燒功能(減少供應量)
 * - 鑄造功能(增加供應量,只有所有者可以執行)
 */
contract MyToken is ERC20, ERC20Burnable, Ownable {
    // 代幣精度(小數位數)
    uint8 private _decimals;
    
    // 代幣最大供應量
    uint256 private _cap;
    
    /**
     * @dev 構造函數
     * @param name 代幣名稱
     * @param symbol 代幣符號
     * @param decimalsValue 小數位數
     * @param initialSupply 初始供應量
     * @param capValue 最大供應量
     */
    constructor(
        string memory name,
        string memory symbol,
        uint8 decimalsValue,
        uint256 initialSupply,
        uint256 capValue
    ) ERC20(name, symbol) Ownable(msg.sender) {
        require(capValue > 0, "ERC20Capped: cap is 0");
        require(initialSupply <= capValue, "ERC20Capped: cap exceeded");
        
        _decimals = decimalsValue;
        _cap = capValue;
        
        // 鑄造初始供應量
        _mint(msg.sender, initialSupply);
    }
    
    /**
     * @dev 返回小數位數
     */
    function decimals() public view override returns (uint8) {
        return _decimals;
    }
    
    /**
     * @dev 返回最大供應量
     */
    function cap() public view returns (uint256) {
        return _cap;
    }
    
    /**
     * @dev 鑄造新代幣(只有所有者可以調用)
     * @param to 接收代幣的地址
     * @param amount 鑄造數量
     */
    function mint(address to, uint256 amount) public onlyOwner {
        require(totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
        _mint(to, amount);
    }
    
    /**
     * @dev 覆寫轉帳前後的鉤子函數
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override(ERC20) {
        super._beforeTokenTransfer(from, to, amount);
    }
}

合約解析

這個合約繼承了三個OpenZeppelin提供的合約:

  1. ERC20:實現基本的代幣功能,包括轉帳餘額管理等
  2. ERC20Burnable:允許持有者自願銷毀(燃燒)他們的代幣
  3. Ownable:實現基本的所有權訪問控制

合約的構造函數接受以下參數:

2.3 編譯智能合約

編譯過程會將Solidity代碼轉換為以太坊虛擬機(EVM)可以執行的字節碼。Hardhat提供了便捷的編譯命令:

npx hardhat compile

編譯成功後,會在artifacts目錄下生成合約的ABI(應用程序二進制接口)和字節碼。

ABI是以太坊智能合約的接口定義,描述了合約中所有可調用的函數、參數類型和返回值。它是客戶端(如Web應用程序)與區塊鏈上合約交互的橋樑。

編譯過程中可能出現的錯誤及解決方法:

錯誤1:Parser Error
原因:語法錯誤,例如缺少分號或括號不匹配
解決:檢查代碼語法

錯誤2:Type Error
原因:類型不匹配,例如嘗試將address賦值給uint256
解決:檢查變量類型

錯誤3:License Error
原因:缺少SPDX許可證聲明
解決:添加// SPDX-License-Identifier: MIT

第三章:部署智能合約

3.1 部署到本地測試網絡

首先,我們在本地測試網絡上部署合約。本地網絡是Hardhat內置的區塊鏈模擬器,無需連接到任何外部網絡。

啟動本地網絡

打開一個終端窗口,啟動Hardhat本地網絡:

npx hardhat node

這會啟動一個本地以太坊節點,默認在http://127.0.0.1:8545上運行。它會生成10個測試帳戶,每個帳戶包含100 ETH(測試代幣,無實際價值)。

編寫部署腳本

scripts目錄下創建部署腳本deploy.js

const hre = require("hardhat");
const fs = require("fs");

async function main() {
  console.log("開始部署 MyToken 合約...");
  
  // 獲取部署帳戶
  const [deployer] = await hre.ethers.getSigners();
  console.log("部署帳戶地址:", deployer.address);
  
  // 部署合約
  // 參數:名稱, 符號, 小數, 初始供應量, 最大供應量
  // 初始供應量需要考慮小數位數:1000 * 10^18 = 1000000000000000000000
  const initialSupply = hre.ethers.parseUnits("1000", 18);
  const maxSupply = hre.ethers.parseUnits("10000", 18);
  
  const MyToken = await hre.ethers.getContractFactory("MyToken");
  const token = await MyToken.deploy(
    "My Token",           // 代幣名稱
    "MTK",                // 代幣符號
    18,                   // 小數位數
    initialSupply,        // 初始供應量
    maxSupply             // 最大供應量
  );
  
  await token.waitForDeployment();
  const contractAddress = await token.getAddress();
  
  console.log("合約部署成功!");
  console.log("合約地址:", contractAddress);
  
  // 保存部署信息到文件
  const deploymentInfo = {
    network: hre.network.name,
    contractAddress: contractAddress,
    deployer: deployer.address,
    timestamp: new Date().toISOString(),
    initialSupply: initialSupply.toString(),
    maxSupply: maxSupply.toString()
  };
  
  fs.writeFileSync(
    "deployment-info.json",
    JSON.stringify(deploymentInfo, null, 2)
  );
  
  // 驗證部署
  console.log("\n部署驗證:");
  console.log("- 代幣名稱:", await token.name());
  console.log("- 代幣符號:", await token.symbol());
  console.log("- 總供應量:", (await token.totalSupply()).toString());
  console.log("- 最大供應量:", (await token.cap()).toString());
  console.log("- 部署者餘額:", (await token.balanceOf(deployer.address)).toString());
}

// 執行主函數並處理錯誤
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

執行部署

在另一個終端窗口執行部署腳本:

npx hardhat run scripts/deploy.js --network localhost

成功部署後,終端會顯示:

開始部署 MyToken 合約...
部署帳戶地址: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
合約部署成功!
合約地址: 0x5FbDB2315678afecb367f032d93F642f64180aa3

部署驗證:
- 代幣名稱: My Token
- 代幣符號: MTK
- 總供應量: 1000000000000000000000
- 最大供應量: 10000000000000000000000
- 部署者餘額: 1000000000000000000000

3.2 部署到公共測試網絡

測試網絡(Testnet)是模擬以太坊主網的公共測試環境,與主網的區別在於測試代幣沒有實際價值。Sepolia是以太坊官方推薦的測試網絡。

獲取測試網絡代幣

在部署到Sepolia之前,需要從水龍頭(faucet)獲取測試ETH:

  1. 打開Sepolia水龍頭網站:https://sepoliafaucet.com
  2. 連接錢包(建議使用測試帳戶)
  3. 請求測試ETH

配置部署

確保.env文件中的私鑰是測試帳戶的私鑰(非真實資金)。然後執行部署:

npx hardhat run scripts/deploy.js --network sepolia

部署成功後,會得到一個Sepolia區塊鏈上的合約地址。例如:

合約部署成功!
合約地址: 0x5FbDB2315678afecb367f032d93F642f64180aa3

驗證合約

可以使用Etherscan Sepolia區塊瀏覽器驗證合約源代碼:

npx hardhat verify --network sepolia CONTRACT_ADDRESS "My Token" "MTK" 18 INITIAL_SUPPLY MAX_SUPPLY

3.3 與已部署合約交互

部署合約後,我們可以通過多種方式與它交互。以下是一些常見的操作:

使用Hardhat控制台交互

npx hardhat console --network localhost

在控制台中可以執行JavaScript代碼:

// 獲取合約實例
const token = await ethers.getContractAt(
  "MyToken", 
  "0x5FbDB2315678afecb367f032d93F642f64180aa3"
);

// 查詢餘額
const balance = await token.balanceOf("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
console.log("餘額:", ethers.formatUnits(balance, 18));

// 轉帳
const tx = await token.transfer("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", ethers.parseUnits("10", 18));
await tx.wait();
console.log("轉帳完成!");

創建交互腳本

scripts目錄下創建interact.js

const hre = require("hardhat");

async function main() {
  // 從部署文件讀取合約地址
  const fs = require("fs");
  const deploymentInfo = JSON.parse(fs.readFileSync("deployment-info.json", "utf8"));
  const contractAddress = deploymentInfo.contractAddress;
  
  // 獲取合約
  const token = await hre.ethers.getContractAt("MyToken", contractAddress);
  
  // 獲取帳戶
  const [account1, account2] = await hre.ethers.getSigners();
  
  console.log("\n=== 與 MyToken 合約交互 ===\n");
  
  // 查詢餘額
  const balance1 = await token.balanceOf(account1.address);
  console.log(`帳戶1 (${account1.address}) 餘額: ${hre.ethers.formatUnits(balance1, 18)} MTK`);
  
  const balance2 = await token.balanceOf(account2.address);
  console.log(`帳戶2 (${account2.address}) 餘額: ${hre.ethers.formatUnits(balance2, 18)} MTK`);
  
  // 轉帳
  const transferAmount = hre.ethers.parseUnits("50", 18);
  console.log(`\n轉帳 ${hre.ethers.formatUnits(transferAmount, 18)} MTK 從帳戶1到帳戶2...`);
  
  const tx = await token.transfer(account2.address, transferAmount);
  await tx.wait();
  
  console.log("轉帳完成!\n");
  
  // 再次查詢餘額
  const newBalance1 = await token.balanceOf(account1.address);
  const newBalance2 = await token.balanceOf(account2.address);
  
  console.log(`帳戶1 新餘額: ${hre.ethers.formatUnits(newBalance1, 18)} MTK`);
  console.log(`帳戶2 新餘額: ${hre.ethers.formatUnits(newBalance2, 18)} MTK`);
}

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

第四章:智能合約測試

4.1 為什麼需要測試

智能合約一旦部署到區塊鏈上就很難修改,而且任何漏洞都可能導致資金損失。因此,充分的測試是智能合約開發中不可或缺的環節。

測試的好處包括:

4.2 編寫測試用例

Hardhat使用JavaScript/TypeScript編寫測試,使用Mocha測試框架和Chai斷言庫。

test目錄下創建測試文件MyToken.js

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("MyToken", function () {
  let token;
  let owner;
  let addr1;
  let addr2;

  // 每個測試用例執行前部署合約
  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();

    const MyToken = await ethers.getContractFactory("MyToken");
    
    // 部署合約:名稱、符號、小數、初始供應、最大供應
    token = await MyToken.deploy(
      "My Token",
      "MTK",
      18,
      ethers.parseUnits("1000", 18),  // 1000代幣
      ethers.parseUnits("10000", 18)  // 10000上限
    );
    await token.waitForDeployment();
  });

  // 測試:代幣名稱和符號
  describe("Token Metadata", function () {
    it("should have the correct name", async function () {
      expect(await token.name()).to.equal("My Token");
    });

    it("should have the correct symbol", async function () {
      expect(await token.symbol()).to.equal("MTK");
    });

    it("should have 18 decimals", async function () {
      expect(await token.decimals()).to.equal(18);
    });
  });

  // 測試:供應量
  describe("Supply", function () {
    it("should have correct total supply", async function () {
      expect(await token.totalSupply()).to.equal(ethers.parseUnits("1000", 18));
    });

    it("should have correct cap", async function () {
      expect(await token.cap()).to.equal(ethers.parseUnits("10000", 18));
    });
  });

  // 測試:餘額查詢
  describe("Balances", function () {
    it("should assign initial supply to owner", async function () {
      expect(await token.balanceOf(owner.address)).to.equal(
        ethers.parseUnits("1000", 18)
      );
    });

    it("should have zero balance for other accounts initially", async function () {
      expect(await token.balanceOf(addr1.address)).to.equal(0);
    });
  });

  // 測試:轉帳功能
  describe("Transfers", function () {
    it("should transfer tokens between accounts", async function () {
      // 從owner轉帳50代幣到addr1
      await token.transfer(addr1.address, ethers.parseUnits("50", 18));
      
      expect(await token.balanceOf(addr1.address)).to.equal(
        ethers.parseUnits("50", 18)
      );
      expect(await token.balanceOf(owner.address)).to.equal(
        ethers.parseUnits("950", 18)
      );
    });

    it("should fail when sender doesn't have enough tokens", async function () {
      // 嘗試轉帳超出餘額的數量,應該失敗
      await expect(
        token.connect(addr1).transfer(owner.address, ethers.parseUnits("1", 18))
      ).to.be.revertedWith("ERC20: transfer amount exceeds balance");
    });

    it("should update balances after transfers", async function () {
      const initialBalance = await token.balanceOf(owner.address);

      // 轉帳100代幣到addr1
      await token.transfer(addr1.address, ethers.parseUnits("100", 18));

      // 轉帳50代幣從addr1到addr2
      await token.connect(addr1).transfer(addr2.address, ethers.parseUnits("50", 18));

      expect(await token.balanceOf(addr1.address)).to.equal(
        ethers.parseUnits("50", 18)
      );
      expect(await token.balanceOf(addr2.address)).to.equal(
        ethers.parseUnits("50", 18)
      );
      expect(await token.balanceOf(owner.address)).to.equal(
        initialBalance - ethers.parseUnits("100", 18)
      );
    });
  });

  // 測試:授權功能
  describe("Allowance", function () {
    it("should approve tokens for delegated transfer", async function () {
      await token.approve(addr1.address, ethers.parseUnits("100", 18));
      expect(
        await token.allowance(owner.address, addr1.address)
      ).to.equal(ethers.parseUnits("100", 18));
    });

    it("should transfer from approved account", async function () {
      await token.approve(addr1.address, ethers.parseUnits("100", 18));

      // addr1 代表 owner 轉帳給 addr2
      await token
        .connect(addr1)
        .transferFrom(owner.address, addr2.address, ethers.parseUnits("50", 18));

      expect(await token.balanceOf(addr2.address)).to.equal(
        ethers.parseUnits("50", 18)
      );
    });
  });

  // 測試:鑄造功能(僅 owner)
  describe("Minting", function () {
    it("should allow owner to mint new tokens", async function () {
      const initialSupply = await token.totalSupply();
      await token.mint(addr1.address, ethers.parseUnits("100", 18));

      expect(await token.totalSupply()).to.equal(
        initialSupply + ethers.parseUnits("100", 18)
      );
      expect(await token.balanceOf(addr1.address)).to.equal(
        ethers.parseUnits("100", 18)
      );
    });

    it("should not allow non-owner to mint", async function () {
      await expect(
        token.connect(addr1).mint(addr1.address, ethers.parseUnits("100", 18))
      ).to.be.revertedWith("Ownable: caller is not the owner");
    });

    it("should not exceed cap when minting", async function () {
      // 嘗試超過上限的鑄造,應該失敗
      await expect(
        token.mint(addr1.address, ethers.parseUnits("9001", 18))
      ).to.be.revertedWith("ERC20Capped: cap exceeded");
    });
  });

  // 測試:燃燒功能
  describe("Burning", function () {
    it("should allow owner to burn tokens", async function () {
      const initialSupply = await token.totalSupply();
      const initialBalance = await token.balanceOf(owner.address);

      // 燃燒100代幣
      await token.burn(ethers.parseUnits("100", 18));

      expect(await token.totalSupply()).to.equal(
        initialSupply - ethers.parseUnits("100", 18)
      );
      expect(await token.balanceOf(owner.address)).to.equal(
        initialBalance - ethers.parseUnits("100", 18)
      );
    });
  });
});

4.3 執行測試

執行測試的命令:

npx hardhat test

成功執行後,會看到類似以下的輸出:

  MyToken
    Token Metadata
      ✔ should have the correct name
      ✔ should have the correct symbol
      ✔ should have 18 decimals
    Supply
      ✔ should have correct total supply
      ✔ should have correct cap
    Balances
      ✔ should assign initial supply to owner
      ✔ should have zero balance for other accounts initially
    Transfers
      ✔ should transfer tokens between accounts
      ✔ should fail when sender doesn't have enough tokens
      ✔ should update balances after transfers
    Allowance
      ✔ should approve tokens for delegated transfer
      ✔ should transfer from approved account
    Minting
      ✔ should allow owner to mint new tokens
      ✔ should not allow non-owner to mint
      ✔ should not exceed cap when minting
    Burning
      ✔ should allow owner to burn tokens

  17 passing

第五章:2026年智能合約開發趨勢

5.1 最新技術發展

截至2026年第一季度,以太坊智能合約開發領域呈現以下趨勢:

Account Abstraction(帳戶抽象)

ERC-4337標準的普及使得智能合約錢包越來越流行。用戶不再需要保管複雜的私鑰,可以通過社交恢復、多因素認證等方式管理資產。主要錢包如Argent、Safe(原Gnosis Safe)都已支持ERC-4337。

Intent Architecture(意圖架構)

「意圖」正在成為區塊鏈交互的新範式。用戶只需表達「想要什麼」(例如「用1000 USDC換取ETH」),Solver網絡會自動找到最優執行路徑。這種模式大大降低了用戶的操作複雜度。

ZK-Rollup集成

ZK-Rollup技術的成熟使得更多應用開始部署到zkEVM兼容的Layer 2網絡。zkSync Era、Starknet等網絡提供了更低的Gas費用和更快的確認速度。

5.2 開發者資源推薦

官方文檔

學習平台

開發工具


結論

本教程涵蓋了以太坊智能合約開發的完整流程:從環境搭建、編寫合約、部署測試到實際交互。通過完成本教程,讀者應該已經掌握了以下技能:

  1. 配置Hardhat開發環境
  2. 理解Solidity語言基礎
  3. 編寫符合ERC-20標準的代幣合約
  4. 部署合約到本地和公共測試網絡
  5. 編寫和執行智能合約測試
  6. 與已部署的合約進行交互

智能合約開發是一個持續學習的過程。建議讀者在完成本教程後,繼續探索以下方向:

區塊鏈技術仍在快速發展中,新的工具和標準不斷湧現。保持學習的熱情和好奇心,將幫助你在這個激動人心的領域中不斷前進。


附錄:常見問題解答

Q:如何處理「Insufficient Funds」錯誤?

A:確保錢包有足夠的ETH支付Gas費用。測試網絡可以使用水龍頭獲取測試ETH。

Q:合約部署失敗怎麼辦?

A:檢查錯誤訊息,常見原因包括:Gas不足、構造函數參數錯誤、網絡連接問題。

Q:如何升級已部署的合約?

A:使用代理模式(Proxy Pattern)。常見方案包括透明代理、可升級代理等。OpenZeppelin提供了完整的升級解決方案。

Q:智能合約可以修改嗎?

A:傳統合約部署後不可修改。但可以通過代理模式實現可升級合約,儘管這會增加複雜性和安全風險。

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!