首页
学习
活动
专区
圈层
工具
发布
49 篇文章
1
《纸上谈兵·solidity》第 0 课:搭建 Solidity 开发环境(三种方式)
2
《纸上谈兵·solidity》第 1 课:部署你的第一个 Solidity 合约
3
《纸上谈兵·solidity》第 2 课:调用、修改、读取,Solidity 合约不是 REST API
4
《纸上谈兵·solidity》第 3 课:事件(Event)机制与链上日志——不是 print,是广播!
5
《纸上谈兵·solidity》第 4 课:Solidity 合约中的错误处理机制(`require`、`revert`、`assert`)和自定义错误
6
《纸上谈兵·solidity》第 5 课:依赖与外部调用 —— 合约交互的风险与防护
7
《纸上谈兵·solidity》第 6 课:Solidity 数据存储布局 —— memory、storage、calldata 傻傻分不清?
8
《纸上谈兵·solidity》第 7 课:Solidity 函数可见性和修饰器 —— public 和 private 不只是权限标签
9
《纸上谈兵·solidity》第 8 课:Solidity 中的继承与接口 —— 模块化不是“复制粘贴”的借口
10
《纸上谈兵·solidity》第 9 课:Solidity 事件与日志机制 —— 合约世界的“printf”工具
11
《纸上谈兵·solidity》第 10 课:Solidity `fallback` / `receive` 函数 —— 合约如何收 ETH 和响应未知调用?
12
《纸上谈兵·solidity》第 11 课:Solidity 错误处理与异常机制 —— 让合约优雅地失败
13
《纸上谈兵·solidity》第 12 课:Solidity 函数选择器与 ABI 编码原理
14
《纸上谈兵·solidity》第 13 课:Solidity 低级调用 call/delegatecall/staticcall —— 直接和 EVM“对话”
15
《纸上谈兵·solidity》第 14 课:Solidity 中的可升级合约模式 —— 从代理合约到透明代理、UUPS 与安全陷阱
16
《纸上谈兵·solidity》第 15 课:Solidity 库与可重用代码
17
《纸上谈兵·solidity》第 16 课:Pull over Push 支付模式与 Check-Effects-Interactions 原则
18
《纸上谈兵·solidity》第 17 课:合约设计模式实战(二)—— Access Control 与权限管理
19
《纸上谈兵·solidity》第 18 课:合约设计模式实战(三)—— 代理 + 插件化架构(Diamond Standard / EIP-2535)
20
《纸上谈兵·solidity》第 19 课:安全专题(一)—— 常见攻击手法与防御
21
《纸上谈兵·solidity》第 20 课:Solidity 安全专题(二)—— 编译器特性与低级漏洞
22
《纸上谈兵·solidity》第 21 课:Gas 优化与成本分析 —— 写出便宜的智能合约
23
《纸上谈兵·solidity》第 22 课:代币合约(ERC20)从零实现与扩展
24
《纸上谈兵·solidity》第 23 课:NFT 合约(ERC721 / ERC1155)实战
25
《纸上谈兵·solidity》第 24 课:去中心化众筹合约(Crowdfunding)实战
26
《纸上谈兵·solidity》第 25 课:简化版的去中心化交易所(DEX)简化版
27
《纸上谈兵·solidity》第 26 课:借贷合约简化实现
28
《纸上谈兵·solidity》第 27 课:DAO 治理合约(去中心化自治组织)
29
《纸上谈兵·solidity》第 28 课:智能合约安全审计案例复盘 -- The DAO Hack(2016)
30
《纸上谈兵·solidity》第 29 课:智能合约安全审计案例复盘 -- Parity Wallet Hack(2017)
31
《纸上谈兵·solidity》第 30 课:智能合约安全审计案例复盘 -- Nomad Bridge(2022)
32
《纸上谈兵·solidity》第 31 课:多签钱包在跨链桥中的应用 —— Nomad 事件复盘
33
《纸上谈兵·solidity》第 32 课:DeFi 基础合约
34
《纸上谈兵·solidity》第 33 课:多签钱包(Multisig Wallet)-- 合约设计与实现
35
《纸上谈兵·solidity》第 34 课:多签钱包(Multisig Wallet)-- 上线
36
《纸上谈兵·solidity》第 35 课:去中心化交易所(DEX)实战 — 合约设计
37
《纸上谈兵·solidity》第 36 课:去中心化交易所(DEX)实战 — 上线
38
《纸上谈兵·solidity》第 37 课:DeFi 实战 -- 资金池与利率模型
39
《纸上谈兵·solidity》第 38 课:DeFi 实战(2) -- 清算机制与价格预言机
40
《纸上谈兵·solidity》第 39 课:DeFi 实战(3) -- 利息累积与 aToken 设计
41
《纸上谈兵·solidity》第 40 课:DeFi 实战(4) -- 风险控制与防护
42
《纸上谈兵·solidity》第 41 课:DeFi 实战(5) -- 协议费与治理
43
《纸上谈兵·solidity》第 42 课:DeFi 实战(6) -- 跨资产借贷与多市场支持
44
《纸上谈兵·solidity》第 43 课:DeFi 实战(7) -- 清算机制进阶(多资产抵押清算路径、拍卖机制)
45
《纸上谈兵·solidity》第 44 课:DeFi 实战(8) -- 利率曲线与资金池优化(动态利用率模型)
46
《纸上谈兵·solidity》第 45 课:DeFi 实战(9) -- 利息累积与结算机制(可复利)
47
《纸上谈兵·solidity》第 46 课:DeFi 实战(10) -- 跨链借贷与流动性桥接
48
《纸上谈兵·solidity》第 47 课:DeFi 实战(11) -- 治理代币 & 激励机制(Tokenomics & Governance)
49
《纸上谈兵·solidity》第 48 课:DeFi 实战(12) -- 前端 DApp 集成与用户交互(React + ethers.js 实战)

《纸上谈兵·solidity》第 27 课:DAO 治理合约(去中心化自治组织)

1、学习目标

  1. 理解 DAO 的核心理念:由代币持有人共同治理
  2. 学习实现 提案(Proposal)+ 投票(Voting)+ 执行(Execution) 流程
  3. 引入 治理代币(Governance Token),绑定投票权
  4. 学习 时间锁 Timelock,防止恶意提案被立即执行

2、DAO 合约设计要点

  • 治理代币:每个代币 = 1 票
  • 提案 Proposal:由用户提交,包含目标地址 + 执行数据
  • 投票 Voting:代币持有人按比例投票,投票期内可投
  • 执行 Execution:提案通过后,由合约调用目标合约
  • 时间锁 Timelock:执行需等待一段时间(例如 2 天)

3、示例合约 SimpleDAO.sol

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title SimpleDAO - 简化版 DAO 治理合约
/// @notice 教学演示用,不可用于生产

interface IERC20 {
    function balanceOf(address account) external view returns (uint);
}

contract SimpleDAO {
    IERC20 public governanceToken;
    uint public proposalCount;
    uint public constant VOTING_PERIOD = 3 days;   // 投票期
    uint public constant TIMELOCK_DELAY = 2 days;  // 执行延迟
    uint public constant QUORUM = 100e18;          // 最低投票总数(100 票)

    enum ProposalState { Active, Defeated, Succeeded, Queued, Executed }

    struct Proposal {
        address proposer;
        address target;
        bytes data;
        string description;
        uint voteFor;
        uint voteAgainst;
        uint startTime;
        uint endTime;
        uint eta; // Estimated Time for execution
        ProposalState state;
    }

    mapping(uint => Proposal) public proposals;
    mapping(uint => mapping(address => bool)) public hasVoted;

    event ProposalCreated(uint id, address proposer, string description);
    event Voted(uint id, address voter, bool support, uint weight);
    event ProposalQueued(uint id, uint eta);
    event ProposalExecuted(uint id);

    constructor(address _token) {
        governanceToken = IERC20(_token);
    }

    /// @notice 创建提案
    function propose(address target, bytes calldata data, string calldata description) external {
        proposalCount++;
        proposals[proposalCount] = Proposal({
            proposer: msg.sender,
            target: target,
            data: data,
            description: description,
            voteFor: 0,
            voteAgainst: 0,
            startTime: block.timestamp,
            endTime: block.timestamp + VOTING_PERIOD,
            eta: 0,
            state: ProposalState.Active
        });

        emit ProposalCreated(proposalCount, msg.sender, description);
    }

    /// @notice 投票
    function vote(uint proposalId, bool support) external {
        Proposal storage proposal = proposals[proposalId];
        require(block.timestamp >= proposal.startTime, "voting not started");
        require(block.timestamp <= proposal.endTime, "voting ended");
        require(!hasVoted[proposalId][msg.sender], "already voted");

        uint weight = governanceToken.balanceOf(msg.sender);
        require(weight > 0, "no voting power");

        if (support) {
            proposal.voteFor += weight;
        } else {
            proposal.voteAgainst += weight;
        }

        hasVoted[proposalId][msg.sender] = true;
        emit Voted(proposalId, msg.sender, support, weight);
    }

    /// @notice 投票结果检查,并进入 Timelock 队列
    function queue(uint proposalId) external {
        Proposal storage proposal = proposals[proposalId];
        require(block.timestamp > proposal.endTime, "voting not ended");
        require(proposal.state == ProposalState.Active, "not active");

        if (proposal.voteFor <= proposal.voteAgainst || proposal.voteFor < QUORUM) {
            proposal.state = ProposalState.Defeated;
        } else {
            proposal.state = ProposalState.Queued;
            proposal.eta = block.timestamp + TIMELOCK_DELAY;
            emit ProposalQueued(proposalId, proposal.eta);
        }
    }

    /// @notice 执行提案
    function execute(uint proposalId) external {
        Proposal storage proposal = proposals[proposalId];
        require(proposal.state == ProposalState.Queued, "not queued");
        require(block.timestamp >= proposal.eta, "timelock not expired");

        (bool success, ) = proposal.target.call(proposal.data);
        require(success, "execution failed");

        proposal.state = ProposalState.Executed;
        emit ProposalExecuted(proposalId);
    }
}

4、测试文件 test/SimpleDAO.t.sol

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/SimpleDAO.sol";

/// @notice 简单的治理代币 (ERC20-like)
contract GovernanceToken is IERC20 {
    string public name = "GovToken";
    string public symbol = "GOV";
    uint8 public decimals = 18;
    uint public totalSupply;
    mapping(address => uint) public balanceOf;

    function mint(address to, uint amount) external {
        balanceOf[to] += amount;
        totalSupply += amount;
    }
}

/// @notice 被治理的目标合约(DAO 将控制它)
contract TargetContract {
    uint public value;

    function setValue(uint _value) external {
        value = _value;
    }
}

contract SimpleDAOTest is Test {
    GovernanceToken public gov;
    SimpleDAO public dao;
    TargetContract public target;

    address alice = address(0x123);
    address bob = address(0x234);

    function setUp() public {
        gov = new GovernanceToken();
        dao = new SimpleDAO(address(gov));
        target = new TargetContract();

        // 给 Alice 和 Bob 铸造治理代币
        gov.mint(alice, 100e18);
        gov.mint(bob, 50e18);
    }

    /// @notice 测试完整的提案生命周期
    function testProposalLifecycle() public {
        vm.startPrank(alice);

        // Alice 提出一个提案:调用 target.setValue(42)
        bytes memory data = abi.encodeWithSignature("setValue(uint256)", 42);
        dao.propose(address(target), data, "Set value to 42");

        vm.stopPrank();

        // Alice 投支持票
        vm.startPrank(alice);
        dao.vote(1, true);
        vm.stopPrank();

        // Bob 投反对票
        vm.startPrank(bob);
        dao.vote(1, false);
        vm.stopPrank();

        // 快进 3 天,投票结束
        vm.warp(block.timestamp + 3 days + 1);

        // 进入 Timelock 队列
        dao.queue(1);

        // 立即执行应失败(需要 timelock)
        vm.expectRevert();
        dao.execute(1);

        // 再快进 2 天
        vm.warp(block.timestamp + 2 days);

        // 执行提案
        dao.execute(1);

        // 验证目标合约的值已被修改
        assertEq(target.value(), 42);
    }
}

执行测试:

代码语言:bash
复制
➜  tutorial git:(main) ✗ forge test --match-path test/SimpleDAO.t.sol -vvv
[⠊] Compiling...
[⠒] Compiling 1 files with Solc 0.8.30
[⠑] Solc 0.8.30 finished in 551.36ms
Compiler run successful!

Ran 1 test for test/SimpleDAO.t.sol:SimpleDAOTest
[PASS] testProposalLifecycle() (gas: 416882)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.95ms (2.33ms CPU time)

Ran 1 test suite in 165.45ms (5.95ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

5、本课总结

  • DAO 合约的基本三步:提案 → 投票 → 执行
  • 引入 时间锁(Timelock) 防止提案立即执行
  • 结合治理代币,DAO 就能实现 链上自治决策
  • 真实项目中,DAO 还会增加:代理投票、提案执行脚本、资金库控制等
下一篇
举报
领券