【Solidity】Solidity by Example

简介: 投票以下合同相当复杂,但展示了很多Solidity的功能。 实行投票合同。 当然,电子投票的主要问题是如何将投票权分配给正确的人,以及如何防止操纵。 我们不会在这里解决所有问题,但至少我们将会展示如何进行委托投票,以便计票同时自动完全透明。

投票

以下合同相当复杂,但展示了很多Solidity的功能。 实行投票合同。 当然,电子投票的主要问题是如何将投票权分配给正确的人,以及如何防止操纵。 我们不会在这里解决所有问题,但至少我们将会展示如何进行委托投票,以便计票同时自动完全透明。

这个想法是每次投票创建一个合同,为每个选项提供一个简短的名称。 然后担任主席的合同的创建者将有权单独投票给每个地址。

地址后面的人可以选择投票自己或将他们的投票委托给他们信任的人。

在投票结束时,获胜winningProposal()将以最多的投票数返回提案。

pragma solidity ^0.4.11;

/// @title 与代表团投票.
contract Ballot {
    //这声明了一个新的复杂类型,稍后将用于变量。 它将代表一个选民。
    struct Voter {
        uint weight; // 由代表团积累的权重
        bool voted;  // 如果true,那个人已经投票了
        address delegate; // 投票给了谁
        uint vote;   // 投票建议的指标
    }

    // 这是单个提案的类型。
    struct Proposal {
        bytes32 name;   // 简称 (up to 32 bytes)
        uint voteCount; // 累计投票数
    }

    address public chairperson;

    // 这声明一个状态变量,为每个可能的地址存储一个“Voter”结构。
    mapping(address => Voter) public voters;

    // 一个动态大小的“Proposal”结构的数组。
    Proposal[] public proposals;

    /// 创建一个新的投票选择“proposalNames”之一。
    function Ballot(bytes32[] proposalNames) {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        // 对于每个提供的提案名称,创建一个新的提案对象,并将其添加到数组的末尾。
        for (uint i = 0; i < proposalNames.length; i++) {
            // `Proposal({...})'创建一个临时的Proposal对象,并且`proposals.push(...)'将它附加到`proposal`的末尾。
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }

    // 给“voter”投票的权利。 只能由“chairperson”打电话。
    function giveRightToVote(address voter) {
        // 如果`require`的参数评估为`false`,它将终止并将所有更改还原为状态和Ether余额。 // 如果函数调用不正确,通常是一个好主意。 但要注意的是,这个目前还将消耗所有提供的gas(这个计划在将来改变)。
        require((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0));
        voters[voter].weight = 1;
    }

    /// 将投票委托给选民“to”。
    function delegate(address to) {
        // 分配参考
        Voter storage sender = voters[msg.sender];
        require(!sender.voted);

        // 不允许自我授权。
        require(to != msg.sender);

        // 只要`to`也转让代表团。
        // 一般来说,这样的循环是非常危险的,因为如果它们运行时间太长,它们可能需要比块中可用的更多的gas。 在这种情况下,代理不会执行,但在其他情况下,这种循环可能导致合同完全“卡住”。
        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;

            // 我们发现在代表团循环,不允许的。 
            require(to != msg.sender);
        }

        // 由于`sender'是一个引用,所以这个修改了`voter [msg.sender] .voted`
        sender.voted = true;
        sender.delegate = to;
        Voter storage delegate = voters[to];
        if (delegate.voted) {
            // 如果代表已经投票,直接增加投票数
            proposals[delegate.vote].voteCount += sender.weight;
        } else {
            // 如果代表没有投票,增加她的权重。
            delegate.weight += sender.weight;
        }
    }

    /// 给你的票(包括委托给你票)提案`proposals[proposal].name`。
    function vote(uint proposal) {
        Voter storage sender = voters[msg.sender];
        require(!sender.voted);
        sender.voted = true;
        sender.vote = proposal;

        // 如果`proposal`超出了数组的范围,这将自动抛出并恢复所有更改。
        proposals[proposal].voteCount += sender.weight;
    }

    /// @dev 计算所有以前的投票的获胜提案。
    function winningProposal() constant
            returns (uint winningProposal)
    {
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++) {
            if (proposals[p].voteCount > winningVoteCount) {
                winningVoteCount = proposals[p].voteCount;
                winningProposal = p;
            }
        }
    }

    // 调用winningProposal()函数来获取提案数组中包含的获胜者的索引,然后返回获胜者的名称
    function winnerName() constant
            returns (bytes32 winnerName)
    {
        winnerName = proposals[winningProposal()].name;
    }
}

上面例子中的require方法的意思是,如果里面的条件为false,后面的代码就不执行了,相当于return false.

可能的改进

目前,需要许多交易才能向所有参与者分配投票权。 你能想到一个更好的方法吗?

盲目拍卖

在本节中,我们将展示在Ethereum创建完全盲目的拍卖合同是多么容易。 我们将从一个公开的拍卖开始,每个人都可以看到所有的出价,然后把这个合同延伸到一个盲目的拍卖,在投标期结束之前不可能看到实际的出价。

简单公开竞价

以下简单拍卖合同的总体思路是,每个人都可以在投标期间发送出价。 出价已经包括汇款/以太网,以将投标人绑定到出价。 如果最高出价提高,先前出价最高者得到她的钱回来。 招标期结束后,合同手续必须手工扣除,受益人才能领取款项 - 合同无法自行投入使用。

pragma solidity ^0.4.11;

contract SimpleAuction {
    // 拍卖参数。 时间是绝对的unix时间戳(从1970-01-01开始的秒)或时间段(以秒为单位)。
    address public beneficiary;
    uint public auctionStart;
    uint public biddingTime;

    // 拍卖的当前状态
    address public highestBidder;
    uint public highestBid;

    // 允许撤销以前的出价
    mapping(address => uint) pendingReturns;

    // 最后设置为true,不允许任何更改
    bool ended;

    // 将在更改时触发的事件。
    event HighestBidIncreased(address bidder, uint amount);
    event AuctionEnded(address winner, uint amount);

    // 以下是所谓的natspec评论,由三个斜杠识别。
    // 当用户被要求确认交易时将显示。

    /// 代表受益人地址“_beneficiary”,以“_biddingTime”秒的投标时间创建一个简单的拍卖。
    function SimpleAuction(
        uint _biddingTime,
        address _beneficiary
    ) {
        beneficiary = _beneficiary;
        auctionStart = now;
        biddingTime = _biddingTime;
    }

    // 以拍卖的价格与本交易一起出价。
    // 如果拍卖未获胜,价值将被退还。
    function bid() payable {
        // 没有必要的参数,所有信息已经是交易的一部分。 功能需要支付的关键字是能够接收Ether。

        // 如果投标期结束,则恢复通话。
        require(now <= (auctionStart + biddingTime));

        // 如果出价不高,发回款。
        require(msg.value > highestBid);

        if (highestBidder != 0) {
            // 通过简单地使用highestBidder.send(highestBid)发回资金是一种安全风险,因为它可以执行不可信合同。
            // 让收件人自己领取钱总是比较安全的。
            pendingReturns[highestBidder] += highestBid;
        }
        highestBidder = msg.sender;
        highestBid = msg.value;
        HighestBidIncreased(msg.sender, msg.value);
    }

    /// 提取过高的投标。
    function withdraw() returns (bool) {
        uint amount = pendingReturns[msg.sender];
        if (amount > 0) {
            // 重要的是将其设置为零,因为在send发送返回之前,收件人可以再次调用此函数作为接收调用的一部分。
            pendingReturns[msg.sender] = 0;

            if (!msg.sender.send(amount)) {
                // 不需要调用扔这里,只是重置量亏欠的
                pendingReturns[msg.sender] = amount;
                return false;
            }
        }
        return true;
    }

    /// 结束拍卖并向受益人发送最高出价。
    function auctionEnd() {
        // 将与其他合同相互作用( 调用方法是或者发送Ether )的功能分为三个阶段是一个很好的指导方针
        // 1. 检查条件
        // 2. 执行动作(潜在的变化条件)
        // 3. 与其他合同交互
        // 如果这些阶段混合起来,另一个合同可以回调到当前合同中,并修改状态或引起影响(Ether支付)多次执行。
        // 如果内部调用的函数包括与外部契约的交互,那么它们也必须被视为与外部契约的交互。

        // 1. 条件
        require(now >= (auctionStart + biddingTime)); // auction did not yet end
        require(!ended); // this function has already been called

        // 2. 影响
        ended = true;
        AuctionEnded(highestBidder, highestBid);

        // 3. 互动
        beneficiary.transfer(highestBid);
    }
}

盲目拍卖

先前公开拍卖延伸到下面的盲拍卖。 盲拍卖的好处是,有对投标期结束时没有时间压力。 在透明的计算平台上进行盲目拍卖可能听起来像是一个矛盾,但是密码学就是派上用场了。

招标期间,投标人实际上并不发送出价,而只是一个散列版本。 由于现在被认为几乎不可能找到散列值相等的两个(足够长的)值,所以投标人就进行出价。 招标期结束后,投标人必须透露投标:他们发送价值未加密,合同检查哈希值与招标期间提供的价值相同。

另一个挑战是如何同时进行拍卖捆绑和盲目:防止投标人在拍卖结束后不发货的唯一方法是将其与投标一起发送。当在Ethereum价值转移不能被蒙蔽,任何人都能看到这个价值。

以下合同通过接受任何大于最高出价的值来解决此问题。 由于这当然只能在揭示阶段进行检查,所以有些投标可能无效,这是有意义的(甚至提供了一个明确的标志,可以将无效出价设置为高价值转账):投标人可以通过放置几个高或 低无效出价。

pragma solidity ^0.4.11;

contract BlindAuction {
    struct Bid {
        bytes32 blindedBid;
        uint deposit;
    }

    address public beneficiary;
    uint public auctionStart;
    uint public biddingEnd;
    uint public revealEnd;
    bool public ended;

    mapping(address => Bid[]) public bids;

    address public highestBidder;
    uint public highestBid;

    // 允许撤销以前的出价
    mapping(address => uint) pendingReturns;

    event AuctionEnded(address winner, uint highestBid);

    /// 修改器是验证函数输入的便捷方式。 `只有Before`应用于下面的`bid`:新的函数体是修饰符体,其中`_`被旧的函数体代替.
    modifier onlyBefore(uint _time) { require(now < _time); _; }
    modifier onlyAfter(uint _time) { require(now > _time); _; }

    function BlindAuction(
        uint _biddingTime,
        uint _revealTime,
        address _beneficiary
    ) {
        beneficiary = _beneficiary;
        auctionStart = now;
        biddingEnd = now + _biddingTime;
        revealEnd = biddingEnd + _revealTime;
    }

    /// 通过这个替换来实现盲目投标: `_blindedBid` = keccak256(value, fake, secret).
    /// 如果投标在曝光阶段正确显示,则发送的ether只能退回。 如果与出价一起发送的ether至少是“价值”,“假”不是真的,投标是有效的。 将“假”设置为true并发送不确切的金额是隐藏实际出价但仍然进行所需存款的方法。 相同的地址可以放置多个出价。
    function bid(bytes32 _blindedBid)
        payable
        onlyBefore(biddingEnd)
    {
        bids[msg.sender].push(Bid({
            blindedBid: _blindedBid,
            deposit: msg.value
        }));
    }

    /// 揭示你的盲目投标。 您将获得所有正确盲目的无效出价和所有出价的退款,除非是最高的。
    function reveal(
        uint[] _values,
        bool[] _fake,
        bytes32[] _secret
    )
        onlyAfter(biddingEnd)
        onlyBefore(revealEnd)
    {
        uint length = bids[msg.sender].length;
        require(_values.length == length);
        require(_fake.length == length);
        require(_secret.length == length);

        uint refund;
        for (uint i = 0; i < length; i++) {
            var bid = bids[msg.sender][i];
            var (value, fake, secret) =
                    (_values[i], _fake[i], _secret[i]);
            if (bid.blindedBid != keccak256(value, fake, secret)) {
                // 投标实际上没有透露
                // 退款押金
                continue;
            }
            refund += bid.deposit;
            if (!fake && bid.deposit >= value) {
                if (placeBid(msg.sender, value))
                    refund -= value;
            }
            // 发送方不可能重新索取相同的存款。
            bid.blindedBid = bytes32(0);
        }
        msg.sender.transfer(refund);
    }

    // 这是一个“内部”功能,这意味着它只能从合同本身(或派生合同)中调用。
    function placeBid(address bidder, uint value) internal
            returns (bool success)
    {
        if (value <= highestBid) {
            return false;
        }
        if (highestBidder != 0) {
            // 退款先前出价最高的人。
            pendingReturns[highestBidder] += highestBid;
        }
        highestBid = value;
        highestBidder = bidder;
        return true;
    }

    /// 提取过高的投标。
    function withdraw() {
        uint amount = pendingReturns[msg.sender];
        if (amount > 0) {
            // 重要的是将其设置为零,因为接收方可以在send发送返回之前再次调用此函数作为接收呼叫的一部分(请参阅上述关于条件 - >效果 - >交互的注释)。
            pendingReturns[msg.sender] = 0;

            msg.sender.transfer(amount);
        }
    }

    /// 结束拍卖并向受益人发送最高出价。
    function auctionEnd()
        onlyAfter(revealEnd)
    {
        require(!ended);
        AuctionEnded(highestBidder, highestBid);
        ended = true;
        // 我们发送所有的钱,因为一些退款可能会失败。
        beneficiary.transfer(this.balance);
    }
}

安全远程购买

pragma solidity ^0.4.11;

contract Purchase {
    uint public value;
    address public seller;
    address public buyer;
    enum State { Created, Locked, Inactive }
    State public state;

    // Ensure that `msg.value` is an even number.
    // Division will truncate if it is an odd number.
    // Check via multiplication that it wasn't an odd number.
    function Purchase() payable {
        seller = msg.sender;
        value = msg.value / 2;
        require((2 * value) == msg.value);
    }

    modifier condition(bool _condition) {
        require(_condition);
        _;
    }

    modifier onlyBuyer() {
        require(msg.sender == buyer);
        _;
    }

    modifier onlySeller() {
        require(msg.sender == seller);
        _;
    }

    modifier inState(State _state) {
        require(state == _state);
        _;
    }

    event Aborted();
    event PurchaseConfirmed();
    event ItemReceived();

    /// Abort the purchase and reclaim the ether.
    /// Can only be called by the seller before
    /// the contract is locked.
    function abort()
        onlySeller
        inState(State.Created)
    {
        Aborted();
        state = State.Inactive;
        seller.transfer(this.balance);
    }

    /// Confirm the purchase as buyer.
    /// Transaction has to include `2 * value` ether.
    /// The ether will be locked until confirmReceived
    /// is called.
    function confirmPurchase()
        inState(State.Created)
        condition(msg.value == (2 * value))
        payable
    {
        PurchaseConfirmed();
        buyer = msg.sender;
        state = State.Locked;
    }

    /// Confirm that you (the buyer) received the item.
    /// This will release the locked ether.
    function confirmReceived()
        onlyBuyer
        inState(State.Locked)
    {
        ItemReceived();
        // It is important to change the state first because
        // otherwise, the contracts called using `send` below
        // can call in again here.
        state = State.Inactive;

        // NOTE: This actually allows both the buyer and the seller to
        // block the refund - the withdraw pattern should be used.

        buyer.transfer(value);
        seller.transfer(this.balance);
    }
}
目录
相关文章
|
JavaScript 前端开发 区块链
【区块链Solidity】智能合约与Solidity介绍
【区块链Solidity】智能合约与Solidity介绍
98 0
|
数据可视化 JavaScript 前端开发
Solidity编码规范
1、命名规范 避免使用 小写的L,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。 合约、库、事件、枚举及结构体命名——大驼峰 合约、库、事件及结构体命名应该使用单词首字母大写的方式。 比如:SimpleToken, SmartBank, CertificateHashRepository,Player。 函数、参数、变量及修饰器 函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,
Solidity编码规范
|
编译器 区块链
|
存储 区块链 数据库
Solidity开发智能合约
一个简单的智能合约 在Solidity中,一个合约由一组代码(合约的函数)和数据(合约的状态)组成。合约位于以太坊区块链上的一个特殊地址。
1481 0
|
存储 Dart Java
|
存储 区块链 索引
Solidity -- CryptoZombies 学习笔记2
第1章 第2章: 映射(Mapping)和地址(Address) Addresses (地址) 以太坊区块链由 account (账户)组成,你可以把它想象成银行账户。
1183 0
|
前端开发 区块链
Solidity -- CryptoZombies 学习笔记1
相关:https://cryptozombies.iohttp://wiki.jikexueyuan.com/project/solidity-zh/ 第一章: 课程概述 第二章: 合约 从最基本的开始入手: Solidity 的代码都包裹在合约里面.
1447 0
|
JSON 区块链 数据格式
如何通过solc编译solidity编写的以太坊智能合约
solc:solidity的编译器 solidity编写的以太坊智能合约可通过命令行编译工具solc来进行编译,成为以太坊虚拟机中的代码。
2294 0
深入理解 Solidity
深入理解 Solidity 此节将帮助你深入理解Solidity,如果有遗漏,请和我们联系Gitter或者在Githhub上发pull request Layout of a Solidity Source File ...
930 0
|
新零售 Web App开发 开发工具
安装Solidity
安装Solidity 基于浏览器的Solidity 如果你只是想尝试一个使用Solidity的小合约,你不需要安装任何东西,只要访问 基于浏览器的Solidity。
1621 0