Dapp 开发实例二

发布于 2024-10-16 21:55:13 字数 4573 浏览 4 评论 0

投票 DApp

当你第一次打开 Remix 就会看到一个 Ballot 的合约,这是关于投票的示例,但是并不完善,下面我们来一步步的改进它。

鉴于已经是本书的第二个实例,因此基本概念不会在赘述,进度相对于实例一会快一些。

梳理思路

假设该智能合约里有一个投票项目,分为红方蓝方,每个投票者只能投一票并且要交保证金,当投票截止日期到了时合约 owner 会触发结算方法,公布结果的同时退还保证金。

因此合约里应该有针对红蓝的计数器、可供外部调用的投票方法、可供外部调用的查询票数统计方法、owner 触发投票结束方法、退款方法。整体流程如下图:

锁定编译版本

这样的源代码文件不会使用早于版本 0.4.0 的编译器进行编译,并且它也不适用于从版本 0.5.0 开始的编译器(这个条件是使用^添加的)。要注意版本声明时 “^” 的意思。

// 不建议
pragma solidity ^0.4.0;

// 建议
pragma solidity 0.4.4;

如建议的那一句所示,我们指定了编译版本,因为在这个版本下我们测试最多,能保证质量。

开发智能合约

按照需求,我们如此开发:

pragma solidity 0.4.4;

contract Vote {
    //检查是否为合约创建者
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    //检测汇入的钱是否够 1 ether
    modifier checkBail(){
        require(msg.value >= bail);
        _;
    }

    //检查是否有该投票者
    modifier checkVoter(){
        require(voters[msg.sender].delegate != 0x0);
        _;
    }

    //检查投票是否结束
    modifier isFinish(){
       require(!finishFlag);
        _; 
    }

    struct Voter {
        uint item;
        address delegate;
        bool isRefund;
    }

    struct Proposal {
        uint voteCount;
    }

    address public owner;
    Proposal red;
    Proposal blue;
    uint constant bail = 1 ether;
    bool finishFlag = false;
    Proposal[] proposals;
    mapping(address => Voter) voters;
    address[] voterList;

    function Vote(uint8 _numProposals) public {
        owner = msg.sender;
        red = Proposal(0);
        blue = Proposal(0);

        proposals.push(red);
        proposals.push(blue);
    }

    function delegate(uint item) payable external checkBail isFinish{
        if(voters[msg.sender].delegate == 0x0){
            var sender = Voter(item, msg.sender, false);
            voters[msg.sender] = sender;
            voterList.push(msg.sender);

            proposals[item].voteCount++;
        }
    }

    function getResult() isFinish external returns(uint result) {
        result = red.voteCount > blue.voteCount ? 0 : 1;
    }

    function finish() onlyOwner{
        finishFlag = true;
    }

    function refundAll() public {
        for(uint x; x < voterList.length; x++) {
            if(!voterList[x].send(bail)) {
                throw;
            }
        }
    }
}

既然是红蓝双方我们就使用 Proposal 来创建了,它们之间的不同仅是票数。与此同时,在构造方法里把 red 和 blue 加进了数组,之后再 delegate 投票时直接使用数组索引就代表了投的是哪项。

因此在这里,我们约定 red 标记为 0,blue 标记为 1。相应的使用 modifier 进行辅助判断,在构造方法里初始化红蓝双方和 owner,设定付款即投票的方法 delegate,只能由合约创建者确定的 finishFlag 标志,公共查询投票结果的 getResult 方法,以及最重要的退款 refundAll 方法。

注意:x.transfer(y) 和 if (!x.send(y)) throw; 是等价的,这里只是为了更清楚的解释而已。建议尽可能使用 transfer。

总结与改进

还记得 refundAll 这个给投票者退款的方法么?在这里有一点需特别注意。refundAll 通过数组迭代来向用户支付退款,但前提是每笔交易都成功,但凡有一笔交易失败将导致所有退款操作回滚,这意味着该循环将永远不会完成。

外部调用合约都有可能失败,为了减少这些损失最好把外部调用逻辑和内部逻辑分开:由收款方负责发起调用该方法。比如让用户自己拿回保证金而不是直接发送给他们,千万注意设好撤回资金的额度

    function withdraw() external checkVoter{
        var voter = voters[msg.sender];

        if(this.balance >= bail && finishFlag && !voter.isRefund){
            if(msg.sender.send(bail)){
                voter.isRefund = true;
            }
        }
    }

比如在这里,废弃原来的 refundAll 方法改用 withdraw,当发送退款成功时再标记 isRefund 为 true。

至此第二个实例的主要部分就已经分析完成了,因为智能合约的安全非常重要,有必要简单介绍下几个著名的漏洞事件,希望读者以史为鉴。

DAO 事件

DAO 是一个数字分散的自治组织,也是一种由投资者主导的风险投资基金。2016 年 6 月 17 日,黑客在智能合约中发现了一个漏洞,他可以从 DAO 中提出 ether。 在袭击的前几个小时内,360 万 ETH 被盗,相当于当时的 7000 万美元。

在这个漏洞攻击中,攻击者能够在智能合约更新其余额之前多次“请求”智能合约(DAO)转账 ether(re-entry 攻击)。 事实上,当 DAO 智能合约创建时,开发人员没有考虑到恶意递归调用的可能性,和智能合约先发送 ETH 资金,然后更新内部余额的缺陷。

Parity 钱包

由于 Parity 客户端发布的多重签名钱包智能合约存在严重漏洞,攻击者可以立即接管钱包(成为合约 owner)并提取所有资金。原因在于合约代码中的基本漏洞未能正确限制外部调用。 在 Solidity 中,没有修饰符的方法被认为是 public。 这意味着该方法可以从任何来源访问,包括对合同进行的外部交易调用。

黑客先向合约发起了一笔 value 为 0 的交易,里面包括在 msg.data 加上 initWallet 的调用:

function() payable {
  // just being sent some cash?
  if (msg.value > 0)
    Deposit(msg.sender, msg.value);
  else if (msg.data.length > 0)
    _walletLibrary.delegatecall(msg.data);
}

而后调用了:

function initWallet(address[] _owners, uint _required, uint _daylimit) { 
    initDaylimit(_daylimit); 
    initMultiowned(_owners, _required); 
}

重新初始化 owner 地址。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

泅渡

暂无简介

0 文章
0 评论
21 人气
更多

推荐作者

玍銹的英雄夢

文章 0 评论 0

我不会写诗

文章 0 评论 0

十六岁半

文章 0 评论 0

浸婚纱

文章 0 评论 0

qq_kJ6XkX

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文