选择Create a JavaScript project(JavaScript编写测试和脚本)
从零开始构建你的第一个DApp合约
以太坊智能合约是什么?
以太坊智能合约(Smart Contract)是一段部署在以太坊区块链上的自动执行代码,它 predefined 了合约参与方的权利与义务,当预设条件被触发时,合约会按约定规则自动执行操作(如转账、数据存储、逻辑判断等),无需第三方信任背书,它就像“写在区块链上的合同”,一旦部署就无法篡改,且由以太坊网络中的所有节点共同维护和执行。
开发以太坊智能合约的核心步骤
开发以太坊智能合约通常包括环境搭建、合约编写、编译、测试、部署、交互六大环节,下面以最主流的Solidity语言和Hardhat框架为例,详细拆解每个步骤。
环境搭建:安装必要工具
开发以太坊合约需要以下基础工具:
- Node.js:JavaScript运行环境(建议版本≥18),用于运行开发框架(如Hardhat)。
- Hardhat:以太坊开发框架,提供编译、测试、部署等一体化功能(推荐新手使用,比Truffle更灵活)。
- MetaMask:浏览器钱包插件,用于管理开发者账户和与测试网/主网交互。
- Remix IDE:在线Solidity编辑器(可选,适合快速原型验证,无需本地环境)。
安装Hardhat:
打开终端,执行以下命令:
npm init -y # 初始化Node.js项目 npm install --save-dev hardhat # 安装Hardhat
合约编写:用Solidity定义逻辑
Solidity是以太坊智能合约的核心编程语言,语法类似JavaScript,但专为区块链设计(支持变量状态、修饰符、事件等特性)。
创建Hardhat项目:
npx hardhat # 初始化Hardhat项目# 按提示输入项目名称、是否添加.gitignore等
编写第一个合约:
在contracts/目录下创建SimpleStorage.sol文件,编写一个简单的“存储合约”,实现“读取/写入数字”功能:
// SPDX-License-Identifier: MIT // 声明许可证(必须)
pragma solidity ^0.8.20; // 声明Solidity版本(建议0.8.0+)
contract SimpleStorage {
uint256 private storedData; // 状态变量:存储一个无符号整数(private仅合约内可访问)
// 事件:当数据变更时触发(前端可监听)
event DataUpdated(uint256 oldData, uint256 newData);
// 写入数据的函数(public:任何地址均可调用)
function set(uint256 _data) public {
uint256 oldData = storedData;
storedData = _data; // 修改状态变量(会消耗gas)
emit DataUpdated(oldData, storedData); // 触发事件
}
// 读取数据的函数(view:不修改状态,免费调用)
function get() public view returns (uint256) {
return storedData;
}
}
合约编译:将Solidity转为字节码
Solidity代码需要编译成以太坊虚拟机(EVM)可执行的字节码(Bytecode)和应用二进制接口(ABI),前者部署到链上,后者用于前端与合约交互。
使用Hardhat编译:
在终端执行:
npx hardhat compile
编译成功后,字节码和ABI会生成在artifacts/contracts/SimpleStorage.sol/SimpleStorage.json文件中。
合约测试:确保逻辑正确
测试是保证合约安全的关键环节,Hardhat支持使用Mocha(测试框架)和Chai(断言库)编写测试用例。
编写测试脚本:
在test/目录下创建simpleStorage.test.js文件:
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("SimpleStorage", function () {
let SimpleStorage;
let simpleStorage;
before(async function () {
// 部署合约
SimpleStorage = await ethers.getContractFactory("SimpleStorage");
simpleStorage = await SimpleStorage.deploy();
await simpleStorage.deployed();
});
it("Should store the value 89.", async function () {
// 调用set函数写入数据
await simpleStorage.set(89);
// 调用get函数读取数据并验证
expect(await simpleStorage.get()).to.equal(89);
});
it("Should emit DataUpdated event.", async function () {
// 监听事件
const tx = await simpleStorage.set(42);
const receipt = await tx.wait();
const event = receipt.events.find(e => e.event === "DataUpdated");
expect(event.args.oldData).to.equal(89);
expect(event.args.newData).to.equal(42);
});
});
运行测试:
npx hardhat test
测试通过后,说明合约逻辑符合预期。
合约部署:将合约上链
部署合约需要账户(用于支付Gas费)和网络(测试网或主网),开发阶段通常使用本地测试网(Hardhat Network)或公共测试网(如Goerli、Sepolia)。
本地测试网部署:
Hardhat默认启动本地节点(端口8545),直接执行部署脚本:
在scripts/目录下创建deploy.js:
async function main() {
const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
const simpleStorage = await SimpleStorage.deploy();
await simpleStorage.deployed();
console.log("SimpleStorage deployed to:", simpleStorage.address);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
执行部署:
npx hardhat run scripts/deploy.js --network localhost
返回合约地址(如0x5FbDB2315678afecb367f032d93F642f64180aa3),即本地部署成功。
公共测试网部署(以Goerli为例):
- 从Goerli水龙头(如
goerli-faucet.pk910.de)获取免费测试ETH(用于支付Gas)。 - 在MetaMask中添加Goerli网络(网络ID:5,RPC:
https://rpc.ankr.com/eth_goerli)。 - 修改
hardhat.config.js,添加Goerli配置:require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config();
/* @type import('hardhat/config').HardhatUserConfig / module.exports = { solidity: "0.8.20", networks: { goerli: { url: process.env.GOERLI_RPC_URL, // 从.env文件读取 accounts: [process.env.PRIVATE_KEY], // MetaMask私钥(勿泄露) }, }, };
在项目根目录创建`.env`文件,填入RPC和私钥:
```env
GOERLI_RPC_URL=https://rpc.ankr.com/eth_goerli
PRIVATE_KEY=你的MetaMask私钥(以0x开头)
- 执行部署:
npx hardhat run scripts/deploy.js --network goerli
部署成功后,合约地址会显示在Goerli浏览器(如
https://goerli.etherscan.io/)上。
合约交互:调用合约功能
部署后,可通过Ethers.js(JavaScript库)与合约交互,例如在前端或脚本中读取/写入数据。
示例脚本(调用合约):
创建interact.js:
const { ethers } = require("hardhat");
async function main() {
const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
const simpleStorage = await SimpleStorage.attach("0x你的合约地址"); // 替换为实际地址
// 读取数据
const storedData = await simpleStorage.get();
console.log("Stored data:", storedData.toString());
// 写入数据
const tx = await simpleStorage.set(100);
await tx.wait();
console.log("Data updated to 100");
// 再次读取
const newData = await simpleStorage.get();
console.log("New stored data:", newData.toString());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
执行脚本:
node interact.js
开发智能合约的注意事项
- 安全性优先:
避免重入攻击(使用`Checks-Effects