/ 币圈行情

以太坊智能合约实战,从零构建你的第一个Demo

发布时间:2026-01-23 14:01:58

以太坊作为全球第二大公有链,其核心创新在于“智能合约”——一种运行在区块链上、自动执行预设规则的程序代码,它无需中介信任,即可实现资产转移、逻辑验证等复杂功能,是DeFi、NFT、DAO等应用的技术基石,本文将以“简易投票系统”为例,带你从零理解以太坊智能合约的编写、部署与交互,完成一个可落地的Demo实践。

智能合约:以太坊的“自动执行器”

智能合约本质上是一段部署在以太坊虚拟机(EVM)上的代码,它以“代码即法律”的方式,在满足预设条件时自动触发执行,投票合约会在投票时间结束后自动统计票数,无需人工干预;借贷合约会在抵押物价值不足时自动清算,这种去中心化、不可篡改的特性,使其成为构建可信应用的核心工具。

开发环境准备:三步搞定“合约工坊”

在编写智能合约前,需完成以下环境搭建,以保障开发流程顺畅:

安装Node.js与npm

智能合约开发依赖Node.js环境,需确保版本≥16.0,从nodejs官网下载安装后,通过终端运行node -vnpm -v验证安装成功。

配置Hardhat框架

Hardhat是当前最流行的以太坊开发框架,支持合约编译、测试、部署等全流程,在终端执行以下命令初始化项目:

mkdir ethereum-contract-demo && cd ethereum-contract-demo  
npm init -y  
npm install --save-dev hardhat  
npx hardhat init  

按提示选择“Create a JavaScript project”,并安装依赖(如@nomicfoundation/hardhat-toolbox)。

安装MetaMask钱包

MetaMask是浏览器端的以太坊钱包,用于管理账户、与合约交互,从MetaMask官网安装浏览器插件,创建并备份好助记词,确保钱包网络切换至“本地开发网络”(后续由Hardhat启动)。

智能合约编写:用Solidity实现“投票逻辑”

本文以“简易投票系统”为例,实现以下功能:

  • 合约部署者创建投票议题,设置投票选项和截止时间;
  • 地址可对指定选项投票(每人限一票);
  • 投票结束后,任何人可查询最终票数。

创建合约文件

contracts目录下新建Voting.sol,编写如下代码:

// SPDX-License-Identifier: MIT  
pragma solidity ^0.8.20;  
contract Voting {  
    // 投票选项结构体  
    struct Option {  
        string name;  
        uint voteCount;  
    }  
    // 状态变量  
    string public votingTopic;  
    uint public votingDeadline;  
    mapping(address => bool) public hasVoted;  
    Option[] public options;  
    // 事件:投票时触发,方便前端监听  
    event Voted(address voter, string option);  
    // 构造函数:部署时初始化投票议题和选项  
    constructor(string memory _topic, string[] memory _optionNames) {  
        votingTopic = _topic;  
        votingDeadline = block.timestamp   1 days; // 投票截止时间:部署后1天  
        for (uint i = 0; i < _optionNames.length; i  ) {  
            options.push(Option(_optionNames[i], 0));  
        }  
    }  
    // 投票函数  
    function vote(uint _optionIndex) public {  
        require(block.timestamp < votingDeadline, "Voting has ended");  
        require(!hasVoted[msg.sender], "You have already voted");  
        require(_optionIndex < options.length, "Invalid option index");  
        hasVoted[msg.sender] = true;  
        options[_optionIndex].voteCount  ;  
        emit Voted(msg.sender, options[_optionIndex].name);  
    }  
    // 查询票数  
    function getVoteCount(uint _optionIndex) public view returns (uint) {  
        require(_optionIndex < options.length, "Invalid option index");  
        return options[_optionIndex].voteCount;  
    }  
    // 获取所有选项  
    function getOptions() public view returns (string[] memory, uint[] memory) {  
        string[] memory optionNames = new string[](options.length);  
        uint[] memory voteCounts = new uint[](options.length);  
        for (uint i = 0; i < options.length; i  ) {  
            optionNames[i] = options[i].name;  
            voteCounts[i] = options[i].voteCount;  
        }  
        return (optionNames, voteCounts);  
    }  
}  

代码解析

  • 状态变量votingTopic(议题)、votingDeadline(截止时间)、hasVoted(记录投票地址)、options(投票选项数组);
  • 构造函数:部署时初始化议题和选项,设置1天的投票时长;
  • vote函数:核心投票逻辑,通过require校验投票时间、重复投票、选项有效性;
  • 事件Voted事件用于前端监听投票行为,提升交互体验。

合约编译与测试:确保代码“零错误”

编译合约

在终端执行:

npx hardhat compile  

成功后,artifacts目录会生成编译后的ABI(应用二进制接口)和字节码,这是合约与交互的“桥梁”。

编写测试用例

test目录下新建voting.test.js,使用Chai测试框架验证合约逻辑:

const { expect } = require("chai");  
const { ethers } = require("hardhat");  
describe("Voting Contract", function () {  
    let votingContract;  
    let owner, voter1, voter2;  
    beforeEach(async function () {  
        [owner, voter1, voter2] = await ethers.getSigners();  
        const Voting = await ethers.getContractFactory("Voting");  
        votingContract = await Voting.deploy("Best Crypto?", ["Bitcoin", "Ethereum"]);  
        await votingContract.waitForDeployment();  
    });  
    it("Should initialize with correct topic and options", async function () {  
        expect(await votingContract.votingTopic()).to.equal("Best Crypto?");  
        const [options, _] = await votingContract.getOptions();  
        expect(options).to.deep.equal(["Bitcoin", "Ethereum"]);  
    });  
    it("Should allow voting before deadline", async function () {  
        await votingContract.connect(voter1).vote(0); // 投票给Bitcoin  
        expect(await votingContract.getVoteCount(0)).to.equal(1);  
        expect(await votingContract.hasVoted(voter1.address)).to.equal(true);  
    });  
    it("Should reject duplicate votes", async function () {  
        await votingContract.connect(voter1).vote(0);  
        await expect(votingContract.connect(voter1).vote(0)).to.be.revertedWith("You have already voted");  
    });  
    it("Should reject voting after deadline", async function () {  
        // 快进时间(Hardhat支持时间快进)  
        await ethers.provider.send("evm_increaseTime", [2 * 24 * 60 * 60]); // 增加2天  
        await ethers.provider.send("evm_mine", []);  
        await expect(votingContract.connect(voter1).vote(0)).to.be.revertedWith("Voting has ended");  
    });  
});  

运行测试

执行以下命令启动本地测试节点(默认Hardhat Network)并运行测试:

npx hardhat test  

若所有测试通过,说明合约逻辑正确,可进入部署阶段。

合约部署:让代码“上链运行”

配置部署脚本

scripts目录下新建deploy.js

async function main() {  
    const Voting = await ethers.getContractFactory("Voting");  
    const votingContract = await Voting.deploy("Best Crypto?", ["Bitcoin", "Ethereum"]);  
    await votingContract.waitForDeployment();  
    console.log("Voting contract deployed to:", votingContract.target);  
}  
main().catch((error) => {  
    console.error(error);  
    process.exitCode = 1;  
});  

部署到本地网络

执行部署命令:

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

成功后,终端会输出合约地址(如0x5FbDB2315678afecb367f032d93F642f64180aa3),此时合约已部署到Hardhat本地网络。

免责声明:本文为转载,非本网原创内容,不代表本网观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

如有疑问请发送邮件至:bangqikeconnect@gmail.com