以太坊,作为区块链2.0的杰出代表,不仅仅是一种加密货币,更是一个去中心化的应用平台,其核心魅力在于智能合约——一种运行在以太坊虚拟机(EVM)上,能够自动执行合约条款的计算机程序,对于想要踏入区块链开发领域的新手来说,亲手编写并部署一个以太坊智能合约Demo,是理解其工作原理的最佳途径,本文将带你一步步完成这个过程。
什么是智能合约?
智能合约是一种“那么”(If-This-Then-That)式的自动化协议,它被部署在区块链上,一旦预设的条件被触发,合约就会自动执行约定的操作,且整个过程透明、不可篡改,以太坊智能合约通常使用Solidity语言编写。
为何需要一个Demo?
一个Demo(演示程序)能够将抽象的概念具体化,通过构建一个简单的Demo,你可以:
- 理解核心概念:如账户、地址、余额、交易、Gas等。
- 熟悉开发流程:从环境搭建、合约编写、编译、部署到与合约交互。
- 获得实践经验:理论知识结合动手实践,印象更深刻。
- 建立开发信心:完成第一个Demo后,你将有能力探索更复杂的智能合约应用。
我们的Demo目标:一个简单的“投票”合约
为了演示智能合约的基本功能,我们将创建一个简单的投票合约,这个合约将允许:
- 创建者(合约部署者)添加多个投票选项。
- 地址(每个地址限一次)可以对其中一个选项进行投票。
- 查询各选项的当前得票数。
构建Demo前的准备:开发环境搭建
在开始编写合约之前,你需要准备以下工具和环境:
- MetaMask钱包:一个浏览器插件钱包,用于管理你的以太坊账户、私钥,并与以太坊网络交互,从MetaMask官网下载并安装,创建一个新钱包并妥善保存助记词。
- 以太坊测试网ETH:为了在测试网络上部署和交互合约,你需要免费的测试网ETH,你可以从水龙头(Faucet)网站获取,如Sepolia测试网的Faucet。
- VS Code:一个流行的代码编辑器,建议安装Solidity插件,它提供语法高亮、代码提示等功能。
- Hardhat:一个流行的以太坊开发环境,用于编译、测试、部署智能合约,它提供了强大的插件系统和调试功能。
- 安装Node.js和npm(或yarn)。
- 在终端中运行
npx hardhat初始化一个新的Hardhat项目。
编写智能合约(Solidity)
在Hardhat项目中,你会在 contracts/ 目录下找到 Lock.sol 文件(默认模板),我们可以将其重命名并修改为 Voting.sol,然后编写如下代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Voting {
// 定义一个投票选项的结构体
struct Candidate {
string name;
uint256 voteCount;
}
// 存储投票选项的映射,选项名称到Candidate结构体
mapping(string => Candidate) public candidates;
// 存储地址是否已投票的映射,防止重复投票
mapping(address => bool) public hasVoted;
// 投票选项的数组
string[] public candidateNames;
// 构造函数,在部署合约时调用,用于初始化投票选项
constructor(string[] memory _candidateNames) {
candidateNames = _candidateNames;
for (uint i = 0; i < _candidateNames.length; i++) {
candidates[_candidateNames[i]] = Candidate({
name: _candidateNames[i],
voteCount: 0
});
}
}
// 投票函数
function vote(string memory candidateName) public {
// 检查投票者是否已经投过票
require(!hasVoted[msg.sender], "You have already voted.");
// 检查候选选项是否存在
require(bytes(candidates[candidateName].name).length > 0, "Candidate does not exist.");
// 记录投票
hasVoted[msg.sender] = true;
candidates[candidateName].voteCount += 1;
}
// 获取候选人的得票数
function getVoteCount(string memory candidateName) public view returns (uint256) {
return candidates[candidateName].voteCount;
}
// 获取所有候选人的名称
function getCandidateNames() public view returns (string[] memory) {
return candidateNames;
}
}
代码解释:
SPDX-License-Identifier和pragma solidity:Solidity合约的标准头部信息,指定许可证和编译器版本。contract Voting:定义了一个名为Voting的智能合约。struct Candidate:定义了候选人结构体,包含名字和得票数。mapping(string => Candidate) public candidates:一个映射,通过选项名称快速找到对应的候选人信息,并设为public以便区块链浏览器访问。mapping(address => bool) public hasVoted:记录每个地址是否已投票。string[] public candidateNames:存储所有候选选项名称的数组。constructor:合约部署时执行的构造函数,用于初始化候选人列表。vote(string memory candidateName):核心投票函数,包含权限检查(是否已投票、候选人是否存在),然后更新投票状态和得票数。getVoteCount和getCandidateNames:查询函数,分别获取特定候选人的得票数和所有候选人名称。
编译与部署合约
-
编译合约: 在Hardhat项目根目录的终端中运行:
npx hardhat compile
如果成功,你会在
artifacts/contracts/目录下看到编译后的合约文件(如Voting.json)。 -
编写部署脚本: 在
scripts/目录下,创建一个新的部署脚本,deploy.js:async function main() { // 获取合约工厂 const Voting = await ethers.getContractFactory("Voting"); // 部署合约,这里我们传入几个候选人名称作为示例 const candidateNames = ["Alice", "Bob", "Charlie"]; const voting = await Voting.deploy(candidateNames); // 等待合约部署完成 await voting.deployed(); console.log("Voting contract deployed to:", voting.address); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); -
配置网络: 在
hardhat.config.js文件中,你需要配置你想要部署到的测试网(如Sepolia),你需要安装@nomicfoundation/hardhat-toolbox插件,并配置你的MetaMask私钥(注意:不要将私钥提交到代码仓库,建议使用环境变量)。require("@nomicfoundation/hardhat-toolbox"); require('dotenv').config(); // 引入dotenv包来管理环境变量 /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.20", networks: { sepolia: { url: process.env.SEPOLIA_URL, // 从.env文件中获取 accounts: [process.env.PRIVATE_KEY] // 从.env文件中获取 } }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY // 可选,用于验证合约 } };
创建
.env文件,并添加你的测试网RPC URL(可以从Alchemy或Infura获取)和MetaMask私钥。 -
部署合约: 确保你的MetaMask钱包连接到了正确的测试网(如Sepolia Test Network),并且有足够的测试ETH。 在终端中运行部署脚本:
npx hardhat run scripts/deploy.js --network sepolia
部署成功后,终端会输出合约的地址,复制这个地址,你可以在以太坊测试网浏览器(如 Sepolia Etherscan)上查看你的合约。
与智能合约交互
-
使用Ethers.js: 你可以编写一个简单的Node.js脚本或前端应用(如使用React + Ethers.js)来调用合约的方法。 创建一个
interact.js脚本:const hre = require("hardhat"); async function main() { // 替换为你的合约地址 const contractAddress = "YOUR_CONTRACT_ADDRESS_HERE"; // 获取合约实例 const Voting = await hre.ethers.getContractFactory("Voting"); const voting = await Voting.attach