从零开始,以太坊智能合约开发实例详解

 :2026-03-06 23:42    点击:43  

以太坊作为全球领先的区块链平台,其核心魅力在于智能合约——一种运行在区块链上、自动执行合约条款的计算机程序,它允许开发者在去中心化的环境中构建各种应用,如去中心化金融(DeFi)、非同质化代币(NFT)、去中心化自治组织(DAO)等,本文将通过一个简单而实用的实例,带您一步步了解以太坊智能合约的开发流程。

开发环境准备

在开始编写智能合约之前,我们需要准备以下开发环境:

  1. Node.js 和 npm:JavaScript 运行时环境和包管理器,从 Node.js 官网 下载并安装 LTS 版本。
  2. Truffle Suite:一套流行的以太坊开发框架,包括 Truffle(开发环境、测试框架和构建工具)、Ganache(个人区块链,用于本地测试)和 Drizzle(与前端交互的库)。
    • 安装:npm install -g truffle
  3. MetaMask:一款浏览器插件钱包,用于与以太坊网络交互(测试网和主网),从 MetaMask 官网 下载并安装。
  4. 代码编辑器:如 VS Code,并安装 Solidity 插件以提供语法高亮和智能提示。

智能合约实例:一个简单的投票合约

我们将开发一个简单的投票合约,允许创建投票提案,并对提案进行投票,合约功能包括:

  • 创建者可以创建新的投票提案。
  • 地址可以投票,且每个地址只能投一次票。
  • 查询提案的描述、得票数以及投票状态。

创建项目结构

随机配图
l>
  • 创建一个新的项目文件夹,voting-dapp
  • 在终端中进入该文件夹,并初始化 Truffle 项目:
    truffle init

    这会创建以下标准目录结构:

    • contracts/:存放 Solidity 智能合约文件。
    • migrations/:存放部署脚本文件。
    • test/:存放测试脚本文件。
    • truffle-config.js:Truffle 配置文件。
  • 编写智能合约

    1. contracts 目录下创建一个新的 Solidity 文件,命名为 Voting.sol
    2. 编写合约代码:
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    contract Voting {
        // 定义提案结构体
        struct Proposal {
            string description; // 描述
            uint voteCount;    // 得票数
        }
        // 存储提案的映射,键为提案名称,值为 Proposal 结构体
        mapping(string => Proposal) public proposals;
        // 存储已投票地址的集合,防止重复投票
        mapping(address => bool) public voters;
        // 提案名称数组
        string[] public proposalNames;
        // 投票是否已结束
        bool public votingEnded;
        // 事件:当新提案创建时触发
        event ProposalCreated(string proposalName);
        // 事件:当有人投票时触发
        event Voted(address voter, string proposalName);
        // 创建提案的函数,仅限合约创建者调用
        function createProposal(string memory _proposalName) public {
            require(!votingEnded, "Voting has ended");
            require(bytes(proposals[_proposalName].description).length == 0, "Proposal already exists");
            proposals[_proposalName] = Proposal({
                description: _proposalName,
                voteCount: 0
            });
            proposalNames.push(_proposalName);
            emit ProposalCreated(_proposalName);
        }
        // 投票函数
        function vote(string memory _proposalName) public {
            require(!votingEnded, "Voting has ended");
            require(bytes(proposals[_proposalName].description).length != 0, "Proposal does not exist");
            require(!voters[msg.sender], "You have already voted");
            voters[msg.sender] = true;
            proposals[_proposalName].voteCount++;
            emit Voted(msg.sender, _proposalName);
        }
        // 结束投票函数,仅限合约创建者调用
        function endVoting() public {
            // 在实际应用中,可能需要添加更复杂的权限控制,比如只有特定角色可以结束投票
            // 这里为了简化,假设任何调用者都可以结束,或者可以添加一个owner变量
            require(!votingEnded, "Voting has already ended");
            votingEnded = true;
        }
        // 重新开始投票函数(可选)
        function restartVoting() public {
            // 同样,这里简化权限控制
            require(votingEnded, "Voting has not ended yet");
            for (uint i = 0; i < proposalNames.length; i++) {
                proposals[proposalNames[i]].voteCount = 0;
            }
            delete voters;
            votingEnded = false;
        }
        // 获取所有提案名称
        function getProposalNames() public view returns (string[] memory) {
            return proposalNames;
        }
        // 获取特定提案的得票数
        function getVoteCount(string memory _proposalName) public view returns (uint) {
            return proposals[_proposalName].voteCount;
        }
    }

    代码解释:

    • SPDX-License-Identifierpragma solidity:Solidity 合约的标准开头,指定许可证和编译器版本。
    • struct Proposal:定义提案的数据结构,包含描述和得票数。
    • mapping(string => Proposal) public proposals:一个映射,用于通过提案名称快速查找提案信息。
    • mapping(address => bool) public voters:一个映射,记录地址是否已投票,防止重复投票。
    • string[] public proposalNames:存储所有提案名称的数组,方便遍历。
    • bool public votingEnded:标记投票是否结束。
    • event:定义事件,用于前端监听合约状态变化。
    • createProposal:创建新提案,要求投票未结束且提案名称唯一。
    • vote:为指定提案投票,要求投票未结束、提案存在且当前地址未投票。
    • endVoting:结束投票,设置 votingEndedtrue
    • restartVoting(可选):重新开始投票,重置所有票数和投票记录。
    • getProposalNamesgetVoteCount:视图函数,用于查询提案信息,不消耗 gas。

    编写部署脚本 (Migration Script)

    Truffle 使用 migrations 目录下的脚本来部署合约。

    1. migrations 目录下创建一个新的文件,命名为 2_deploy_voting.js(数字前缀表示部署顺序)。
    const Voting = artifacts.require("Voting");
    module.exports = function(deployer) {
      // 部署 Voting 合约
      deployer.deploy(Voting);
    };

    编译与测试

    1. 编译合约: 在终端中运行:

      truffle compile

      如果成功,build/contracts 目录下会生成 Voting.json 文件,这是合约的 ABI(应用程序二进制接口)和字节码。

    2. 本地测试

      • 启动 Ganache:打开 Ganache,选择 "QUICKSTART"(它会创建一个本地区块链,并提供 10 个测试账户,每个账户有 100 个 ETH)。
      • 配置 Truffle:打开 truffle-config.js,确保 networks 部分配置了 Ganache 的网络信息(默认情况下,Ganache 监听 HTTP://127.0.0.1:7545,端口可能需要根据 Ganache 启动信息调整)。
        // 示例配置,确保与 Ganache 一致
        module.exports = {
          networks: {
            development: {
              host: "127.0.0.1",
              port: 7545, // Ganache 默认端口
              network_id: "*", // 匹配任何网络 id
            },
          },
          compilers: {
            solc: {
              version: "0.8.0", // 必须与合约中 pragma solidity 版本匹配或兼容
            },
          },
        };
      • 运行迁移脚本
        truffle migrate --network development

        这会执行 migrations 目录下的脚本,将合约部署到 Ganache 创建的本地区块链上,成功后,终端会显示合约的部署地址。

    与合约交互

    1. 使用 Truffle Console

      truffle console --network development

      进入控制台后,可以与部署的合约实例交互:

      // 获取合约实例
      let votingInstance = await Voting.deployed();
      // 创建提案

    本文由用户投稿上传,若侵权请提供版权资料并联系删除!

    热门文章