引言
在区块链技术蓬勃发展的今天,智能合约已经成为去中心化应用(DApp)的核心组件。无论你是想创建自己的代币、开发去中心化金融(DeFi)应用,还是构建NFT市场,掌握智能合约开发都是必不可少的一步。本文将从零开始,手把手教你编写、测试并部署你的第一个智能合约到以太坊测试网。
环境准备
1. 安装必要的工具
首先,我们需要安装以下开发工具:
Node.js 和 npm
# 检查是否已安装
node --version
npm --version
# 如果未安装,请访问 https://nodejs.org 下载安装
Hardhat(以太坊开发框架)
npm install --save-dev hardhat
MetaMask(浏览器钱包插件)
- 从官方网站 metamask.io 下载并安装
- 创建钱包并妥善保存助记词
2. 创建项目
# 创建项目目录
mkdir my-first-smart-contract
cd my-first-smart-contract
# 初始化npm项目
npm init -y
# 初始化Hardhat项目
npx hardhat init
# 选择"Create a JavaScript project"
# 一路回车使用默认配置
编写第一个智能合约
1. 理解智能合约基础
智能合约是用Solidity语言编写的,运行在以太坊虚拟机(EVM)上的程序。它类似于一个自动执行的数字协议,一旦部署就无法修改。
2. 创建简单的代币合约
在 contracts 目录下创建 MyToken.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract MyToken {
// 状态变量
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
// 地址到余额的映射
mapping(address => uint256) public balanceOf;
// 事件:用于前端监听
event Transfer(address indexed from, address indexed to, uint256 value);
event Mint(address indexed to, uint256 value);
// 构造函数:部署时执行一次
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
uint256 _initialSupply
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
// 将初始供应量分配给合约部署者
balanceOf[msg.sender] = _initialSupply;
totalSupply = _initialSupply;
emit Transfer(address(0), msg.sender, _initialSupply);
}
// 转账函数
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0), "Invalid address");
require(balanceOf[msg.sender] >= _value, "Insufficient balance");
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
// 铸造新代币(仅合约所有者可用)
function mint(address _to, uint256 _value) public {
// 在实际项目中,这里应该添加权限控制
balanceOf[_to] += _value;
totalSupply += _value;
emit Mint(_to, _value);
emit Transfer(address(0), _to, _value);
}
// 查询余额
function getBalance(address _account) public view returns (uint256) {
return balanceOf[_account];
}
}
3. 合约代码解析
让我们详细分析这个合约的关键部分:
状态变量
name和symbol:代币名称和符号decimals:小数位数(通常为18)totalSupply:总供应量balanceOf:存储每个地址的余额
事件
- 事件允许前端应用监听合约状态变化
indexed参数使事件可过滤
安全注意事项
require语句用于验证条件- 防止整数溢出(Solidity 0.8+ 自动检查)
- 地址验证防止转账到零地址
编写测试用例
1. 安装测试依赖
npm install --save-dev @nomicfoundation/hardhat-toolbox
2. 创建测试文件
在 test 目录下创建 MyToken.test.js:
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyToken", function () {
let MyToken;
let myToken;
let owner;
let addr1;
let addr2;
let addrs;
beforeEach(async function () {
// 获取合约工厂
MyToken = await ethers.getContractFactory("MyToken");
// 获取测试账户
[owner, addr1, addr2, ...addrs] = await ethers.getSigners();
// 部署合约
myToken = await MyToken.deploy(
"My Test Token",
"MTT",
18,
ethers.parseEther("1000000") // 100万代币
);
await myToken.waitForDeployment();
});
describe("部署", function () {
it("应该设置正确的代币信息", async function () {
expect(await myToken.name()).to.equal("My Test Token");
expect(await myToken.symbol()).to.equal("MTT");
expect(await myToken.decimals()).to.equal(18);
expect(await myToken.totalSupply()).to.equal(
ethers.parseEther("1000000")
);
});
it("应该将初始供应量分配给部署者", async function () {
const ownerBalance = await myToken.balanceOf(owner.address);
expect(ownerBalance).to.equal(ethers.parseEther("1000000"));
});
});
describe("转账", function () {
it("应该允许代币转账", async function () {
// 转账100代币给addr1
const transferAmount = ethers.parseEther("100");
await myToken.transfer(addr1.address, transferAmount);
// 验证余额变化
const addr1Balance = await myToken.balanceOf(addr1.address);
expect(addr1Balance).to.equal(transferAmount);
const ownerBalance = await myToken.balanceOf(owner.address);
expect(ownerBalance).to.equal(
ethers.parseEther("1000000").sub(transferAmount)
);
});
it("应该拒绝余额不足的转账", async function () {
const initialBalance = await myToken.balanceOf(addr1.address);
// addr1尝试转账,但余额为0
await expect(
myToken.connect(addr1).transfer(owner.address, 1)
).to.be.revertedWith("Insufficient balance");
});
it("应该拒绝转账到零地址", async function () {
await expect(
myToken.transfer(ethers.ZeroAddress, 1)
).to.be.revertedWith("Invalid address");
});
});
describe("铸造", function () {
it("应该允许铸造新代币", async function () {
const mintAmount = ethers.parseEther("500");
const initialSupply = await myToken.totalSupply();
await myToken.mint(addr1.address, mintAmount);
const newSupply = await myToken.totalSupply();
expect(newSupply).to.equal(initialSupply.add(mintAmount));
const addr1Balance = await myToken.balanceOf(addr1.address);
expect(addr1Balance).to.equal(mintAmount);
});
});
});
3. 运行测试
npx hardhat test
如果一切正常,你会看到所有测试通过。
配置网络和部署脚本
1. 配置Hardhat网络
修改 hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
networks: {
// 本地开发网络
hardhat: {
chainId: 31337,
},
// Sepolia测试网
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
// Goerli测试网(已弃用,仅作示例)
goerli: {
url: process.env.GOERLI_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},