今天我们要聊一个在区块链世界里超级火热的话题——DAO(去中心化自治组织,DecentralizedAutonomousOrganization)。DAO就像一个链上的“民主社区”,通过智能合约让成员共同决策、管理资金或资源,摆脱中心化控制。如果你玩过DeFi、NFT或者Web3项目,可能会听说
今天我们要聊一个在区块链世界里超级火热的话题——DAO(去中心化自治组织,Decentralized Autonomous Organization)。DAO就像一个链上的“民主社区”,通过智能合约让成员共同决策、管理资金或资源,摆脱中心化控制。如果你玩过DeFi、NFT或者Web3项目,可能会听说过Aragon、Moloch或者The DAO这些名字。DAO的核心是去中心化治理,成员通过投票决定提案,比如花钱、升级合约或调整规则。
DAO是运行在区块链上的组织,通过智能合约自动执行规则和决策。它的核心思想是“代码即法律”,成员通过持有代币或投票权参与治理,共同决定组织的行为。DAO的典型特点包括:
DAO的常见应用场景:
在Solidity中实现DAO,核心是设计投票机制、提案管理和资金分配。我们会通过一个实际例子——一个简单的DAO合约(SimpleDAO),逐步实现这些功能。
为了让大家快速上手,我们来写一个SimpleDAO
合约,功能包括:
先来看合约的框架,包含核心状态变量和初始化逻辑:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
contract SimpleDAO {
IERC20 public governanceToken;
address public admin;
uint public constant VOTING_PERIOD = 3 days;
uint public constant MINIMUM_QUORUM = 100 ether; // 假设代币单位是wei
uint public proposalCount;
struct Proposal {
uint id;
address proposer;
address to;
uint value;
bytes data;
uint voteFor;
uint voteAgainst;
uint startTime;
bool executed;
mapping(address => bool) voted;
}
mapping(uint => Proposal) public proposals;
mapping(address => bool) public isMember;
event ProposalCreated(uint indexed proposalId, address indexed proposer, address to, uint value, bytes data);
event Voted(uint indexed proposalId, address indexed voter, bool support, uint weight);
event ProposalExecuted(uint indexed proposalId, bool success);
event MemberAdded(address indexed member);
event MemberRemoved(address indexed member);
constructor(address _governanceToken) {
governanceToken = IERC20(_governanceToken);
admin = msg.sender;
}
}
代码分析:
IERC20
定义了治理代币的接口(查询余额、转账)。governanceToken
:治理代币合约地址。admin
:初始管理员,控制成员管理。VOTING_PERIOD
:投票持续时间(3天)。MINIMUM_QUORUM
:最低投票参与量(总票数需达到100代币)。proposalCount
:跟踪提案数量,生成唯一ID。Proposal
:记录提案详情,包括ID、提议者、目标地址、金额、数据、投票数、开始时间、是否执行和投票记录。proposals
:用ID映射到提案。isMember
:记录谁是DAO成员。这个框架为DAO打下了基础,接下来实现核心功能。
只有持有治理代币的地址可以成为成员,由管理员管理:
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can call this function");
_;
}
function addMember(address _member) external onlyAdmin {
require(_member != address(0), "Invalid member address");
require(!isMember[_member], "Already a member");
require(governanceToken.balanceOf(_member) > 0, "No governance tokens");
isMember[_member] = true;
emit MemberAdded(_member);
}
function removeMember(address _member) external onlyAdmin {
require(isMember[_member], "Not a member");
isMember[_member] = false;
emit MemberRemoved(_member);
}
代码分析:
onlyAdmin
限制只有管理员能调用。addMember
:确保地址有效、不是成员且持有代币。removeMember
:确保是现有成员。MemberAdded
和MemberRemoved
,记录成员变更。只有成员可以提交提案(比如转ETH):
modifier onlyMember() {
require(isMember[msg.sender], "Not a member");
require(governanceToken.balanceOf(msg.sender) > 0, "No governance tokens");
_;
}
function submitProposal(address _to, uint _value, bytes memory _data) external onlyMember {
uint proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.id = proposalId;
proposal.proposer = msg.sender;
proposal.to = _to;
proposal.value = _value;
proposal.data = _data;
proposal.startTime = block.timestamp;
proposal.executed = false;
emit ProposalCreated(proposalId, msg.sender, _to, _value, _data);
}
代码分析:
onlyMember
确保调用者是成员且持有代币。proposalCount++
生成唯一ID。data
字段支持调用其他合约(比如转ERC20代币)。ProposalCreated
,记录提案详情。成员根据代币持有量投票支持或反对:
function vote(uint _proposalId, bool _support) external onlyMember {
Proposal storage proposal = proposals[_proposalId];
require(block.timestamp <= proposal.startTime + VOTING_PERIOD, "Voting period ended");
require(!proposal.voted[msg.sender], "Already voted");
require(!proposal.executed, "Proposal already executed");
uint weight = governanceToken.balanceOf(msg.sender);
require(weight > 0, "No voting power");
proposal.voted[msg.sender] = true;
if (_support) {
proposal.voteFor += weight;
} else {
proposal.voteAgainst += weight;
}
emit Voted(_proposalId, msg.sender, _support, weight);
}
代码分析:
VOTING_PERIOD
)。governanceToken.balanceOf
计算投票权重。Voted
,记录投票详情。注意:投票权重基于当前余额,可能导致“最后一秒投票”问题(稍后优化)。
投票结束后,任何人可以调用executeProposal
执行通过的提案:
function executeProposal(uint _proposalId) external {
Proposal storage proposal = proposals[_proposalId];
require(block.timestamp > proposal.startTime + VOTING_PERIOD, "Voting period not ended");
require(!proposal.executed, "Proposal already executed");
require(proposal.voteFor + proposal.voteAgainst >= MINIMUM_QUORUM, "Quorum not reached");
require(proposal.voteFor > proposal.voteAgainst, "Proposal not approved");
proposal.executed = true;
(bool success, ) = proposal.to.call{value: proposal.value}(proposal.data);
require(success, "Execution failed");
emit ProposalExecuted(_proposalId, success);
}
代码分析:
MINIMUM_QUORUM
)。call
执行提案,支持ETH转账或合约调用。success
确保调用成功。ProposalExecuted
。整合以上代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
contract SimpleDAO {
IERC20 public governanceToken;
address public admin;
uint public constant VOTING_PERIOD = 3 days;
uint public constant MINIMUM_QUORUM = 100 ether;
uint public proposalCount;
struct Proposal {
uint id;
address proposer;
address to;
uint value;
bytes data;
uint voteFor;
uint voteAgainst;
uint startTime;
bool executed;
mapping(address => bool) voted;
}
mapping(uint => Proposal) public proposals;
mapping(address => bool) public isMember;
event ProposalCreated(uint indexed proposalId, address indexed proposer, address to, uint value, bytes data);
event Voted(uint indexed proposalId, address indexed voter, bool support, uint weight);
event ProposalExecuted(uint indexed proposalId, bool success);
event MemberAdded(address indexed member);
event MemberRemoved(address indexed member);
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can call this function");
_;
}
modifier onlyMember() {
require(isMember[msg.sender], "Not a member");
require(governanceToken.balanceOf(msg.sender) > 0, "No governance tokens");
_;
}
constructor(address _governanceToken) {
governanceToken = IERC20(_governanceToken);
admin = msg.sender;
}
function addMember(address _member) external onlyAdmin {
require(_member != address(0), "Invalid member address");
require(!isMember[_member], "Already a member");
require(governanceToken.balanceOf(_member) > 0, "No governance tokens");
isMember[_member] = true;
emit MemberAdded(_member);
}
function removeMember(address _member) external onlyAdmin {
require(isMember[_member], "Not a member");
isMember[_member] = false;
emit MemberRemoved(_member);
}
function submitProposal(address _to, uint _value, bytes memory _data) external onlyMember {
uint proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.id = proposalId;
proposal.proposer = msg.sender;
proposal.to = _to;
proposal.value = _value;
proposal.data = _data;
proposal.startTime = block.timestamp;
proposal.executed = false;
emit ProposalCreated(proposalId, msg.sender, _to, _value, _data);
}
function vote(uint _proposalId, bool _support) external onlyMember {
Proposal storage proposal = proposals[_proposalId];
require(block.timestamp <= proposal.startTime + VOTING_PERIOD, "Voting period ended");
require(!proposal.voted[msg.sender], "Already voted");
require(!proposal.executed, "Proposal already executed");
uint weight = governanceToken.balanceOf(msg.sender);
require(weight > 0, "No voting power");
proposal.voted[msg.sender] = true;
if (_support) {
proposal.voteFor += weight;
} else {
proposal.voteAgainst += weight;
}
emit Voted(_proposalId, msg.sender, _support, weight);
}
function executeProposal(uint _proposalId) external {
Proposal storage proposal = proposals[_proposalId];
require(block.timestamp > proposal.startTime + VOTING_PERIOD, "Voting period not ended");
require(!proposal.executed, "Proposal already executed");
require(proposal.voteFor + proposal.voteAgainst >= MINIMUM_QUORUM, "Quorum not reached");
require(proposal.voteFor > proposal.voteAgainst, "Proposal not approved");
proposal.executed = true;
(bool success, ) = proposal.to.call{value: proposal.value}(proposal.data);
require(success, "Execution failed");
emit ProposalExecuted(_proposalId, success);
}
}
这个版本已经能跑,但还有优化空间,接下来我们会加入高级功能和安全措施。
基础版功能齐全,但离生产环境还差一些。我们来优化以下方面:
当前投票基于实时代币余额,可能导致用户在投票截止前临时购买代币增加权重。我们改用快照机制,在提案创建时记录投票权重:
struct Proposal {
uint id;
address proposer;
address to;
uint value;
bytes data;
uint voteFor;
uint voteAgainst;
uint startTime;
bool executed;
mapping(address => bool) voted;
mapping(address => uint) voteWeight;
}
function submitProposal(address _to, uint _value, bytes memory _data) external onlyMember {
uint proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.id = proposalId;
proposal.proposer = msg.sender;
proposal.to = _to;
proposal.value = _value;
proposal.data = _data;
proposal.startTime = block.timestamp;
proposal.executed = false;
proposal.voteWeight[msg.sender] = governanceToken.balanceOf(msg.sender);
emit ProposalCreated(proposalId, msg.sender, _to, _value, _data);
}
function vote(uint _proposalId, bool _support) external onlyMember {
Proposal storage proposal = proposals[_proposalId];
require(block.timestamp <= proposal.startTime + VOTING_PERIOD, "Voting period ended");
require(!proposal.voted[msg.sender], "Already voted");
require(!proposal.executed, "Proposal already executed");
uint weight = governanceToken.balanceOf(msg.sender);
require(weight > 0, "No voting power");
proposal.voted[msg.sender] = true;
proposal.voteWeight[msg.sender] = weight;
if (_support) {
proposal.voteFor += weight;
} else {
proposal.voteAgainst += weight;
}
emit Voted(_proposalId, msg.sender, _support, weight);
}
分析:
submitProposal
记录提议者的投票权重,vote
使用当前余额但记录快照。voteWeight
映射,略微增加存储成本。当前提案只支持转账,我们添加成员管理和参数调整提案:
enum ProposalType { Transfer, AddMember, RemoveMember, UpdateQuorum }
struct Proposal {
uint id;
address proposer;
address to;
uint value;
bytes data;
uint voteFor;
uint voteAgainst;
uint startTime;
bool executed;
ProposalType proposalType;
mapping(address => bool) voted;
mapping(address => uint) voteWeight;
}
function submitAddMemberProposal(address _newMember) external onlyMember {
require(_newMember != address(0), "Invalid member address");
require(!isMember[_newMember], "Already a member");
uint proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.id = proposalId;
proposal.proposer = msg.sender;
proposal.to = address(this);
proposal.value = 0;
proposal.data = abi.encodeWithSignature("addMember(address)", _newMember);
proposal.startTime = block.timestamp;
proposal.executed = false;
proposal.proposalType = ProposalType.AddMember;
proposal.voteWeight[msg.sender] = governanceToken.balanceOf(msg.sender);
emit ProposalCreated(proposalId, msg.sender, address(this), 0, proposal.data);
}
分析:
ProposalType
区分转账、添加成员等。submitAddMemberProposal
简化成员管理提案的创建。MINIMUM_QUORUM
可类似实现。executeProposal
的call
在状态更新后执行,防止回调攻击。onlyAdmin
和onlyMember
确保操作合法。block.timestamp
可能被矿工操纵,3天窗口降低风险。添加查询函数:
function getProposal(uint _proposalId) external view returns (
uint id,
address proposer,
address to,
uint value,
bytes memory data,
uint voteFor,
uint voteAgainst,
uint startTime,
bool executed,
ProposalType proposalType
) {
Proposal storage proposal = proposals[_proposalId];
return (
proposal.id,
proposal.proposer,
proposal.to,
proposal.value,
proposal.data,
proposal.voteFor,
proposal.voteAgainst,
proposal.startTime,
proposal.executed,
proposal.proposalType
);
}
function hasVoted(uint _proposalId, address _voter) external view returns (bool) {
return proposals[_proposalId].voted[_voter];
}
分析:
getProposal
返回提案详情,hasVoted
检查投票状态。voteWeight
增加存储成本,可用事件记录投票权重减少存储。整合优化后的代码(部分省略重复功能):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
contract SimpleDAO {
IERC20 public governanceToken;
address public admin;
uint public constant VOTING_PERIOD = 3 days;
uint public constant MINIMUM_QUORUM = 100 ether;
uint public proposalCount;
enum ProposalType { Transfer, AddMember, RemoveMember, UpdateQuorum }
struct Proposal {
uint id;
address proposer;
address to;
uint value;
bytes data;
uint voteFor;
uint voteAgainst;
uint startTime;
bool executed;
ProposalType proposalType;
mapping(address => bool) voted;
mapping(address => uint) voteWeight;
}
mapping(uint => Proposal) public proposals;
mapping(address => bool) public isMember;
event ProposalCreated(uint indexed proposalId, address indexed proposer, address to, uint value, bytes data);
event Voted(uint indexed proposalId, address indexed voter, bool support, uint weight);
event ProposalExecuted(uint indexed proposalId, bool success);
event MemberAdded(address indexed member);
event MemberRemoved(address indexed member);
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can call this function");
_;
}
modifier onlyMember() {
require(isMember[msg.sender], "Not a member");
require(governanceToken.balanceOf(msg.sender) > 0, "No governance tokens");
_;
}
constructor(address _governanceToken) {
governanceToken = IERC20(_governanceToken);
admin = msg.sender;
}
function addMember(address _member) external onlyAdmin {
require(_member != address(0), "Invalid member address");
require(!isMember[_member], "Already a member");
require(governanceToken.balanceOf(_member) > 0, "No governance tokens");
isMember[_member] = true;
emit MemberAdded(_member);
}
function removeMember(address _member) external onlyAdmin {
require(isMember[_member], "Not a member");
isMember[_member] = false;
emit MemberRemoved(_member);
}
function submitProposal(address _to, uint _value, bytes memory _data) external onlyMember {
uint proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.id = proposalId;
proposal.proposer = msg.sender;
proposal.to = _to;
proposal.value = _value;
proposal.data = _data;
proposal.startTime = block.timestamp;
proposal.executed = false;
proposal.proposalType = ProposalType.Transfer;
proposal.voteWeight[msg.sender] = governanceToken.balanceOf(msg.sender);
emit ProposalCreated(proposalId, msg.sender, _to, _value, _data);
}
function submitAddMemberProposal(address _newMember) external onlyMember {
require(_newMember != address(0), "Invalid member address");
require(!isMember[_newMember], "Already a member");
uint proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.id = proposalId;
proposal.proposer = msg.sender;
proposal.to = address(this);
proposal.value = 0;
proposal.data = abi.encodeWithSignature("addMember(address)", _newMember);
proposal.startTime = block.timestamp;
proposal.executed = false;
proposal.proposalType = ProposalType.AddMember;
proposal.voteWeight[msg.sender] = governanceToken.balanceOf(msg.sender);
emit ProposalCreated(proposalId, msg.sender, address(this), 0, proposal.data);
}
function vote(uint _proposalId, bool _support) external onlyMember {
Proposal storage proposal = proposals[_proposalId];
require(block.timestamp <= proposal.startTime + VOTING_PERIOD, "Voting period ended");
require(!proposal.voted[msg.sender], "Already voted");
require(!proposal.executed, "Proposal already executed");
uint weight = governanceToken.balanceOf(msg.sender);
require(weight > 0, "No voting power");
proposal.voted[msg.sender] = true;
proposal.voteWeight[msg.sender] = weight;
if (_support) {
proposal.voteFor += weight;
} else {
proposal.voteAgainst += weight;
}
emit Voted(_proposalId, msg.sender, _support, weight);
}
function executeProposal(uint _proposalId) external {
Proposal storage proposal = proposals[_proposalId];
require(block.timestamp > proposal.startTime + VOTING_PERIOD, "Voting period not ended");
require(!proposal.executed, "Proposal already executed");
require(proposal.voteFor + proposal.voteAgainst >= MINIMUM_QUORUM, "Quorum not reached");
require(proposal.voteFor > proposal.voteAgainst, "Proposal not approved");
proposal.executed = true;
(bool success, ) = proposal.to.call{value: proposal.value}(proposal.data);
require(success, "Execution failed");
emit ProposalExecuted(_proposalId, success);
}
function getProposal(uint _proposalId) external view returns (
uint id,
address proposer,
address to,
uint value,
bytes memory data,
uint voteFor,
uint voteAgainst,
uint startTime,
bool executed,
ProposalType proposalType
) {
Proposal storage proposal = proposals[_proposalId];
return (
proposal.id,
proposal.proposer,
proposal.to,
proposal.value,
proposal.data,
proposal.voteFor,
proposal.voteAgainst,
proposal.startTime,
proposal.executed,
proposal.proposalType
);
}
function hasVoted(uint _proposalId, address _voter) external view returns (bool) {
return proposals[_proposalId].voted[_voter];
}
receive() external payable {}
}
分析:
DAO常需管理ERC20代币,我们添加专用提案:
function submitERC20TransferProposal(address _token, address _to, uint _amount) external onlyMember {
uint proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.id = proposalId;
proposal.proposer = msg.sender;
proposal.to = _token;
proposal.value = 0;
proposal.data = abi.encodeWithSignature("transfer(address,uint256)", _to, _amount);
proposal.startTime = block.timestamp;
proposal.executed = false;
proposal.proposalType = ProposalType.Transfer;
proposal.voteWeight[msg.sender] = governanceToken.balanceOf(msg.sender);
emit ProposalCreated(proposalId, msg.sender, _token, 0, proposal.data);
}
分析:
transfer
调用的数据,支持任何ERC20代币。call
执行,兼容标准ERC20合约。如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!