第三章:智能合约设计与开发

🚀 引言
智能合约是去中心化应用的核心,它们定义了应用的业务逻辑和规则。在本章中,我们将设计并实现一个去中心化投票系统的智能合约。我们将从基本概念开始,逐步构建一个功能完整、安全可靠的投票系统。
想象一下,我们正在为一个社区、组织或公司创建一个透明的投票系统,让所有成员都能参与决策过程,并且每个人都能验证投票的公正性。这就是我们的目标!
📝 投票系统需求分析
在开始编码之前,让我们先明确我们的投票系统需要满足哪些需求:
功能需求
非功能需求
🔍 Solidity语言基础
在深入投票合约之前,让我们先快速回顾一下Solidity的基础知识。
Solidity是什么?
Solidity是一种面向对象的高级编程语言,专门用于实现智能合约。它的语法类似于JavaScript,但有一些重要的区别和特性。
合约结构
一个基本的Solidity合约结构如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
// 状态变量
uint public myVariable;
// 事件
event ValueChanged(uint oldValue, uint newValue);
// 构造函数
constructor(uint initialValue) {
myVariable = initialValue;
}
// 函数
function setValue(uint _newValue) public {
uint oldValue = myVariable;
myVariable = _newValue;
emit ValueChanged(oldValue, _newValue);
}
}
数据类型
Solidity支持多种数据类型:
值类型:
- bool:布尔值(true/false)
- int/uint:有符号/无符号整数(不同位数,如uint8, uint256)
- address:以太坊地址(20字节)
- bytes:字节数组
- enum:枚举类型
引用类型:
- string:字符串
- array:数组(固定大小或动态)
- struct:结构体
- mapping:键值映射(类似哈希表)
函数修饰符
Solidity中的函数可以有不同的可见性和状态修饰符:
-
可见性:
- public:任何人都可以调用
- private:只能在合约内部调用
- internal:只能在合约内部和继承合约中调用
- external:只能从合约外部调用
-
状态修饰符:
- view:不修改状态(只读取)
- pure:不读取也不修改状态
- payable:可以接收以太币
自定义修饰符
Solidity允许创建自定义修饰符,用于在函数执行前后添加条件检查:
modifier onlyOwner() {
require(msg.sender == owner, \”Not the owner\”);
_; // 继续执行函数主体
}
function restrictedFunction() public onlyOwner {
// 只有合约拥有者才能执行的代码
}
事件
事件用于记录合约中发生的重要操作,前端应用可以监听这些事件:
event Transfer(address indexed from, address indexed to, uint amount);
function transfer(address to, uint amount) public {
// 转账逻辑
emit Transfer(msg.sender, to, amount);
}
🏗️ 投票合约设计
现在,让我们开始设计我们的投票系统合约。我们将采用模块化的方法,将系统分解为几个关键组件。
数据结构设计
首先,我们需要定义投票系统的核心数据结构:
// 投票议题
struct Ballot {
uint id;
string title;
string description;
uint startTime;
uint endTime;
bool finalized;
address creator;
}
// 候选人/选项
struct Candidate {
uint id;
string name;
string info;
uint voteCount;
}
// 投票记录
struct Vote {
address voter;
uint candidateId;
uint timestamp;
}
状态变量
接下来,我们需要定义合约的状态变量来存储这些数据:
// 存储所有投票议题
mapping(uint => Ballot) public ballots;
uint public ballotCount;
// 存储每个投票议题的候选人
mapping(uint => mapping(uint => Candidate)) public candidates;
mapping(uint => uint) public candidateCounts;
// 记录谁已经投过票
mapping(uint => mapping(address => bool)) public hasVoted;
// 存储投票记录
mapping(uint => Vote[]) public votes;
// 投票权管理
mapping(address => bool) public voters;
uint public voterCount;
// 合约拥有者
address public owner;
事件定义
我们需要定义一些事件来记录重要操作:
event BallotCreated(uint ballotId, string title, address creator);
event CandidateAdded(uint ballotId, uint candidateId, string name);
event VoterAdded(address voter);
event VoteCast(uint ballotId, address voter, uint candidateId);
event BallotFinalized(uint ballotId, uint winningCandidateId);
💻 实现投票合约
现在,让我们开始实现我们的投票合约。创建一个新文件contracts/VotingSystem.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import \”@openzeppelin/contracts/access/Ownable.sol\”;
import \”@openzeppelin/contracts/security/ReentrancyGuard.sol\”;
/**
* @title 去中心化投票系统
* @dev 一个基于区块链的透明投票系统
*/
contract VotingSystem is Ownable, ReentrancyGuard {
// 数据结构
struct Ballot {
uint id;
string title;
string description;
uint startTime;
uint endTime;
bool finalized;
address creator;
}
struct Candidate {
uint id;
string name;
string info;
uint voteCount;
}
struct Vote {
address voter;
uint candidateId;
uint timestamp;
}
// 状态变量
mapping(uint => Ballot) public ballots;
uint public ballotCount;
mapping(uint => mapping(uint => Candidate)) public candidates;
mapping(uint => uint) public candidateCounts;
mapping(uint => mapping(address => bool)) public hasVoted;
mapping(uint => Vote[]) private votes;
mapping(address => bool) public voters;
uint public voterCount;
// 事件
event BallotCreated(uint ballotId, string title, address creator);
event CandidateAdded(uint ballotId, uint candidateId, string name);
event VoterAdded(address voter);
event VoteCast(uint ballotId, address voter, uint candidateId);
event BallotFinalized(uint ballotId, uint winningCandidateId);
/**
* @dev 构造函数
*/
constructor() {
// 合约部署者自动成为管理员
}
/**
* @dev 创建新的投票议题
* @param _title 投票标题
* @param _description 投票描述
* @param _startTime 开始时间(Unix时间戳)
* @param _endTime 结束时间(Unix时间戳)
*/
function createBallot(
string memory _title,
string memory _description,
uint _startTime,
uint _endTime
) public onlyOwner {
require(_startTime >= block.timestamp, \”Start time must be in the future\”);
require(_endTime > _startTime, \”End time must be after start time\”);
uint ballotId = ballotCount++;
ballots[ballotId] = Ballot({
id: ballotId,
title: _title,
description: _description,
startTime: _startTime,
endTime: _endTime,
finalized: false,
creator: msg.sender
});
emit BallotCreated(ballotId, _title, msg.sender);
}
/**
* @dev 为投票添加候选人/选项
* @param _ballotId 投票ID
* @param _name 候选人名称
* @param _info 候选人信息
*/
function addCandidate(
uint _ballotId,
string memory _name,
string memory _info
) public onlyOwner {
require(_ballotId < ballotCount, \”Ballot does not exist\”);
require(block.timestamp < ballots[_ballotId].startTime, \”Voting has already started\”);
uint candidateId = candidateCounts[_ballotId]++;
candidates[_ballotId][candidateId] = Candidate({
id: candidateId,
name: _name,
info: _info,
voteCount: 0
});
emit CandidateAdded(_ballotId, candidateId, _name);
}
/**
* @dev 添加有投票权的用户
* @param _voter 用户地址
*/
function addVoter(address _voter) public onlyOwner {
require(!voters[_voter], \”Address is already a voter\”);
voters[_voter] = true;
voterCount++;
emit VoterAdded(_voter);
}
/**
* @dev 批量添加有投票权的用户
* @param _voters 用户地址数组
*/
function addVoters(address[] memory _voters) public onlyOwner {
for (uint i = 0; i < _voters.length; i++) {
if (!voters[_voters[i]]) {
voters[_voters[i]] = true;
voterCount++;
emit VoterAdded(_voters[i]);
}
}
}
/**
* @dev 投票
* @param _ballotId 投票ID
* @param _candidateId 候选人ID
*/
function vote(uint _ballotId, uint _candidateId) public nonReentrant {
require(voters[msg.sender], \”You don\’t have voting rights\”);
require(_ballotId < ballotCount, \”Ballot does not exist\”);
require(_candidateId < candidateCounts[_ballotId], \”Candidate does not exist\”);
require(!hasVoted[_ballotId][msg.sender], \”You have already voted in this ballot\”);
Ballot storage ballot = ballots[_ballotId];
require(block.timestamp >= ballot.startTime, \”Voting has not started yet\”);
require(block.timestamp <= ballot.endTime, \”Voting has ended\”);
require(!ballot.finalized, \”Ballot has been finalized\”);
// 记录投票
hasVoted[_ballotId][msg.sender] = true;
// 增加候选人票数
candidates[_ballotId][_candidateId].voteCount++;
// 存储投票记录
votes[_ballotId].push(Vote({
voter: msg.sender,
candidateId: _candidateId,
timestamp: block.timestamp
}));
emit VoteCast(_ballotId, msg.sender, _candidateId);
}
/**
* @dev 获取投票结果
* @param _ballotId 投票ID
* @return 候选人ID数组和对应的票数数组
*/
function getBallotResults(uint _ballotId) public view returns (uint[] memory, uint[] memory) {
require(_ballotId < ballotCount, \”Ballot does not exist\”);
uint candidateCount = candidateCounts[_ballotId];
uint[] memory candidateIds = new uint[](candidateCount);
uint[] memory voteCounts = new uint[](candidateCount);
for (uint i = 0; i < candidateCount; i++) {
candidateIds[i] = i;
voteCounts[i] = candidates[_ballotId][i].voteCount;
}
return (candidateIds, voteCounts);
}
/**
* @dev 获取投票的获胜者
* @param _ballotId 投票ID
* @return 获胜候选人ID
*/
function getWinner(uint _ballotId) public view returns (uint) {
require(_ballotId < ballotCount, \”Ballot does not exist\”);
require(block.timestamp > ballots[_ballotId].endTime, \”Voting has not ended yet\”);
uint winningCandidateId = 0;
uint winningVoteCount = 0;
for (uint i = 0; i < candidateCounts[_ballotId]; i++) {
if (candidates[_ballotId][i].voteCount > winningVoteCount) {
winningVoteCount = candidates[_ballotId][i].voteCount;
winningCandidateId = i;
}
}
return winningCandidateId;
}
/**
* @dev 结束投票并确认结果
* @param _ballotId 投票ID
*/
function finalizeBallot(uint _ballotId) public onlyOwner {
require(_ballotId < ballotCount, \”Ballot does not exist\”);
require(block.timestamp > ballots[_ballotId].endTime, \”Voting has not ended yet\”);
require(!ballots[_ballotId].finalized, \”Ballot already finalized\”);
uint winningCandidateId = getWinner(_ballotId);
ballots[_ballotId].finalized = true;
emit BallotFinalized(_ballotId, winningCandidateId);
}
/**
* @dev 获取投票的详细信息
* @param _ballotId 投票ID
* @return 投票标题、描述、开始时间、结束时间、是否已结束、创建者
*/
function getBallotDetails(uint _ballotId) public view returns (
string memory,
string memory,
uint,
uint,
bool,
address
) {
require(_ballotId < ballotCount, \”Ballot does not exist\”);
Ballot storage ballot = ballots[_ballotId];
return (
ballot.title,
ballot.description,
ballot.startTime,
ballot.endTime,
ballot.finalized,
ballot.creator
);
}
/**
* @dev 获取候选人详细信息
* @param _ballotId 投票ID
* @param _candidateId 候选人ID
* @return 候选人名称、信息、票数
*/
function getCandidateDetails(uint _ballotId, uint _candidateId) public view returns (
string memory,
string memory,
uint
) {
require(_ballotId < ballotCount, \”Ballot does not exist\”);
require(_candidateId < candidateCounts[_ballotId], \”Candidate does not exist\”);
Candidate storage candidate = candidates[_ballotId][_candidateId];
return (
candidate.name,
candidate.info,
candidate.voteCount
);
}
/**
* @dev 检查用户是否有投票权
* @param _voter 用户地址
* @return 是否有投票权
*/
function hasVotingRights(address _voter) public view returns (bool) {
return voters[_voter];
}
/**
* @dev 检查用户是否已在特定投票中投票
* @param _ballotId 投票ID
* @param _voter 用户地址
* @return 是否已投票
*/
function hasVotedInBallot(uint _ballotId, address _voter) public view returns (bool) {
return hasVoted[_ballotId][_voter];
}
}
🔒 安全考虑
在开发智能合约时,安全性是最重要的考虑因素之一。我们的合约已经包含了一些安全措施:
但我们还可以考虑更多的安全措施:
防止前端运行攻击
在以太坊网络中,交易在被打包进区块前是公开的,这可能导致前端运行攻击。对于投票系统,这可能不是主要问题,但在其他应用中需要考虑。
整数溢出保护
Solidity 0.8.0及以上版本已经内置了整数溢出检查,但如果使用较低版本,应该使用SafeMath库。
权限分离
我们可以实现更细粒度的权限控制,例如区分管理员和投票创建者的角色。
🧪 测试合约
测试是确保合约正确性和安全性的关键步骤。让我们创建一个测试文件test/VotingSystem.test.js:
const {
expect }
