Decentralized Autonomous Organization (在以太坊上,如何创建DAO-去中心化自治组织?)

原文:https://www.ethereum.org/dao

翻译:terryc007

版本:v1.0 中英混合

时间: 2018.4.13

"On the Blockchain, no one knows you're a fridge"

  • Richard Brown

“在区块链世界,没有人知道你是一个冰箱”

  • Richard Brown

So far, all contracts we listed were owned and executed by other accounts probably held by humans. But there is no discrimination against robots or humans in the Ethereum ecosystem and contracts can create arbitrary actions like any other account would. Contracts can own tokens, participate in crowdsales, and even be voting members of other contracts.

到目前为止,我们所列出的合约都属于由人类控制的账号所拥有,执行。 但是在以太坊生态中,机器人,跟人类并不会被区别对待。合约可以做任何其他账号能做的事情。合约可以拥有token,参与众筹,并且还能成为其他合约的投票成员。

In this section we are going to build a decentralized and democratic organization that exists solely on the blockchain, but that can do anything that a simple account would be able to. The organization has a central manager that decides who are the members and the voting rules, but as we'll see, this can also be changed.

在本节,我们将在区块链上,创建一个单独地去中心化民主组织,它能做简单账号能做的任何事情。这个组织有个中心管理者,它决定谁可是成为成员,制定投票规则,但是我们会看到,这些也是也可以更改的。

The way this particular democracy works is that it has an Owner which works like an administrator, CEO or a President. The Ownercan add (or remove) voting members to the organization. Any member can make a proposal, which is in the form of an ethereum transaction to either send ether or execute some contract, and other members can vote in support or against the proposal. Once a predetermined amount of time and a certain number of members has voted, the proposal can be executed: the contract counts the votes and if there are enough votes it will execute the given transaction.

这个民主组织是是以这种方式来运作的:它有一个拥有者,就像一个管理员,CEO, 或总统那样工作。 拥有者能添加,删除投票成员。任何成员可以提出一个提议,这个提议以以太坊交易的形式,来发送ETH或执行合约,其他成员可以投票支持或反对。一旦达到预先设定的时间,一定票数,这个提议就会被执行: 合约会计算票数,如果有足够的票数,它就执行指定的交易。

The Blockchain Congress

THE CODE

区块链国会

代码

pragma solidity ^0.4.16;

contract owned {
  address public owner;

  function owned() public {
    owner = msg.sender;
  }

  modifier onlyOwner {
    require(msg.sender == owner);
    _;
  }

  function transferOwnership(address newOwner) onlyOwner public {
    owner = newOwner;
  }
}

contract tokenRecipient {

  event ReceivedEther(address sender, uint amount);
  event ReceivedTokens(address _from, uint256 _value, address _token, bytes _extraData);

  function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public {
    Token t = Token(_token);
    require(t.transferFrom(_from, this, _value));
    emit ReceivedTokens(_from, _value, _token, _extraData);
  }

  function () payable public{
    emit ReceivedEther(msg.sender, msg.value);
  }
}

interface Token {
  function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
}

contract Congress is owned, tokenRecipient {
  // Contract Variables and events
  uint public minimumQuorum;
  uint public debatingPeriodInMinutes;
  int public majorityMargin;
  Proposal[] public proposals;
  uint public numProposals;
  mapping (address => uint) public memberId;
  Member[] public members;

  event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
  event Voted(uint proposalID, bool position, address voter, string justification);
  event ProposalTallied(uint proposalID, int result, uint quorum, bool active);
  event MembershipChanged(address member, bool isMember);
  event ChangeOfRules(uint newMinimumQuorum, uint newDebatingPeriodInMinutes, int newMajorityMargin);

  struct Proposal{
    address recipient;
    uint amount;
    string description;
    uint minExecutionDate;
    bool executed;
    bool proposalPassed;
    uint numberOfVotes;
    int currentResult;
    bytes32 proposalHash;
    Vote[] votes;
    mapping (address => bool) voted;
  }

  struct Member{
    address member;
    string name;
    uint memberSince;
  }

  struct Vote{
    bool inSupport;
    address voter;
    string justification;
  }

  // Modifer that allows only sharholders to vote and craete new proposals
  modifier onlyMembers{
    require(memberId[msg.sender] != 0);
    _;
  }

  /**
   * Construct function
  **/
  function Congress(
    uint minimumQuorumForProposals,
    uint minutesForDebate,
    int marginOfVotesForMajority
  ) public {
    changeVotingRules(minimumQuorumForProposals, minutesForDebate, marginOfVotesForMajority);
    // It's necessary to add an empty first member
    addMember(0, "");
    // and let's add the founder , to save a step later
    addMember(owner, 'founder');
  }

  /**
   * Add member
   *
   * Make 'targetMember' a member named 'memberName'
   *
   * @param targetMember ethereum address to be added
   * @param memberName public name for that member
  **/
  function addMember(address targetMember, string memberName) onlyOwner public {
    uint id = memberId[targetMember];
    if (id == 0)
    {
      memberId[targetMember] = members.length;
      id = members.length++;
    }

    members[id] = Member({member: targetMember, memberSince: now, name: memberName});
    emit MembershipChanged(targetMember, true);
  }

  /**
  *  Remove member
  *
  * @notice Remove membership from 'targetMember'
  *
  * @param targetMember ethereum address to be removed
  **/
  function removeMember(address targetMember) onlyOwner public {
    require(memberId[targetMember] != 0);

    for(uint i = memberId[targetMember]; i < members.length-1; i++)
    {
      members[i] = members[i+1];
    }
    delete members[members.length-1];
    members.length--;
  }

  /**
   * Change voting rules
   *
   * Make so that proposals need to be discussed for at least 'minituesForDebate/60' hours,
   * have at least 'minimumQuorumForProposals' votes, and have 50%+ 'marginOfVotesForMajority' votes
   *
   * @param minimumQuorumForProposals How many members must vote on a proposal for it to be executed
   * @param minutesForDebate The minimum amount of delay between when a proposal is mad and when it can be executed
   * @param marginOfVotesForMajority The proposal needs to have 50% plus this number
  **/
  function changeVotingRules(
    uint minimumQuorumForProposals,
    uint minutesForDebate,
    int marginOfVotesForMajority
    ) onlyOwner public {
       minimumQuorum = minimumQuorumForProposals;
       debatingPeriodInMinutes = minutesForDebate;
       majorityMargin = marginOfVotesForMajority;

       emit ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, majorityMargin);
    }

    /**
    *  Add Proposal
    *
    * Propose to send 'weiAmount / 1e18 ' ether to 'beneficiary' for 'jobDescription'.
    * 'transactionBytecode ? Contains : Does not contain' code.
    *
    * @param beneficiary Who to send the ether to
    * @param weiAmount Amount of ether to send , in wei
    * @param jobDescription Description of job
    * @param transactionBytecode bytecode of transaction
    **/
    function newProposal(
      address beneficiary,
      uint weiAmount,
      string jobDescription,
      bytes transactionBytecode
      ) onlyMembers public returns (uint proposalID)
      {
        proposalID = proposals.length++;
        Proposal storage p = proposals[proposalID];
        p.recipient = beneficiary;
        p.amount = weiAmount;
        p.description = jobDescription;
        p.proposalHash = keccak256(beneficiary, weiAmount, transactionBytecode);
        p.minExecutionDate = now + debatingPeriodInMinutes * 1 minutes;
        p.executed = false;
        p.proposalPassed = false;
        p.numberOfVotes = 0;
        emit ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
        numProposals = proposalID + 1;

        return proposalID;
      }

      /**
       * Add proposal in Ether
       *
       * Propose to send 'etherAmount' ether to 'beneficiary' for 'jobDescription'. 'transactionBytecode ? Contains: Does not contain' code
       * This is a convenience function to use if the amount to be given is in round number of either units
       *
       * @param beneficiary Who to send the ether to
       * @param etherAmount Amount of ether to send
       * @param jobDescription Description of job
       * @param transactionBytecode Bytecode of transaction
      **/
      function newProposalInEther(
        address beneficiary,
        uint etherAmount,
        string jobDescription,
        bytes transactionBytecode
        ) onlyMembers public returns (uint proposalID)
        {
          return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode);
        }

        /**
         * Check if a proposal code matchs
         *
         * @param proposalNumber ID number of the proposal to query
         * @param beneficiary Who to send the ether to
         * @param weiAmount Amount of ether to send
         * @param transactionBytecode Bytecode of transaction
        **/
        function checkProposalCode(
          uint proposalNumber,
          address beneficiary,
          uint weiAmount,
          bytes transactionBytecode
          ) constant public  returns (bool codeChecksOut)
          {
            Proposal storage p = proposals[proposalNumber];
            return p.proposalHash == keccak256(beneficiary, weiAmount, transactionBytecode);
          }

          /**
           * Log a vote for a proposal
           *
           * Vote 'supportsProposal? in support of : against' proposal #'proposalNumber'
           *
           * @param proposalNumber Number of proposal
           * @param supportsProposal Either in favor or against it
           *@param justificationText Optional justification text
          **/
          function vote(
            uint proposalNumber,
            bool supportsProposal,
            string justificationText
            ) onlyMembers public returns (uint voteID)
            {

              Proposal storage p = proposals[proposalNumber]; // Get the proposal
              require(!p.voted[msg.sender]);                  // If has already voted, cancel
              p.voted[msg.sender] = true;                     // Set this voter as having voted
              p.numberOfVotes++;                             // Increase the number of votes
              if (supportsProposal)                           // If they support the proposal
              {                                               // Increase score
                p.currentResult++;
              }
              else{
                p.currentResult--;                            // If the don't, decrease the score
              }

              // Create a log fo this event
              emit Voted(proposalNumber, supportsProposal, msg.sender, justificationText);
              return p.numberOfVotes;
            }

            /**
            * Finish vote
            *
            * Count the votes proposal #'proposalNumber' and excute it if approved
            *
            * @param proposalNumber Proposal number
            * @param transactionBytecode Optional : if the transaction contained a bytecode, you need to send it
            **/
            function executeProposal(uint proposalNumber, bytes transactionBytecode) public {
              Proposal storage p = proposals[proposalNumber];

              require(now > p.minExecutionDate                                                 // If it is past the voting deadline
                   && !p.executed                                                             // and it has not already been executed
                   && p.proposalHash == keccak256(p.recipient, p.amount, transactionBytecode) // and the supplied code matchs the proposal
                   && p.numberOfVotes >= minimumQuorum);                                      // and a minimum quorum has been reached, then execute result
              if (p.currentResult > majorityMargin)
              {
                // Proposal passed; execute the transaction

                p.executed = true; // Avoid recursive calling
                require(p.recipient.call.value(p.amount)(transactionBytecode));

                p.proposalPassed = true;
              }
              else
              {
                // Proposal failed
                p.proposalPassed = false;
              }

              // Fire Events
              emit ProposalTallied(proposalNumber, p.currentResult, p.numberOfVotes, p.proposalPassed);
            }
}

HOW TO DEPLOY

如何部署

Open the wallet (if you are only testing, go to the menu develop > network > testnet), go to the Contracts tab and then press deploy contract, and on the solidity code box, paste the code above. On the contract picker, choose Congress and you'll see the setup variables.

打开以太坊钱包(如果仅是测试,去菜单 develop > network > Pinkeby - Test Nextwork), 再到Contracts标签页,然后点击deploy contract, 在solidity code区域,粘贴上面的代码。在合约选取器,选择Congress,你会看到一些需要设置的变量。

  • Minimum quorum for proposals is the minimum amount of votes a proposal needs to have before it can be executed.
  • Minutes for debate is the minimum amount of time (in minutes) that needs to pass before it can be executed
  • Margin of votes for majority A proposal passes if there are more than 50% of the votes plus the margin. Leave at 0 for simple majority, put it at the number of members - 1 to require an absolute consensus.
  • 提议最少成员数: 提议被执行最少需要的票数。
  • 辩论分钟数:在提议能被执行前,最少需要经过的时间(按分钟算)
  • 额外票数比例: 提议必须有50%以上的票数,再加上额外票数比例,才能通过。 最简单就设置为0,要获得100%的票,那就设置为1。
DAO Setup

You can change these parameters later. As a start, you can choose 5 minutes for debate time and leave the remaining parameters at 0. A little lower on the page, you'll see an estimate of the cost for deploying your contract in ether. You can try lowering the price if you want to save, but that might mean having to wait longer for your contract to be created. Click Deploy, type your password and wait.

这些参数你可以以后再修改。一开始,可以选择5分钟辩论时间,其他的都保留为0. 在这个页面底部,你会看到部署这个合约大约需要花费多少ETH。 如果你想省点钱,你可以试着调低这个价格,不过这可能会让合约的创建时间会更长。 点击Deploy,输入你的密码,然后等待合约部署成功。

In a few seconds you'll be taken to the dashboard, scroll down and you'll be able to see your transaction being created. In under a minute you'll see the transaction successful and a new unique icon will have been created. Click the contract's name to see it (you can get to it at any time on the Contracts tab).

几秒后,会自动跳到钱包主页,滚到到底部,你能看到你刚才创建的交易。1分钟左右,交易成功,并会创建一个新的icon。 点击这个合约名字去查看下(你可以在Contract页面,随时查看它)。

DAO Just created

SHARING WITH OTHERS

分享给其他人

If you want to share your DAO with others, then they need both the contract address and the interface file, a small text string that works as an instruction manual of the contract. Click copy address to get the former and show interface to reveal the latter.

如果你想分享你的DAO给其他人,他们需要DAO的合约地址,以及它的接口文件 — 它是一串很小的字符,就像这个合约的说明手册,告诉区块链这个合约是如何工作的。 点击copy address复制Dao合约地址,点击show interface显示接口字符串。

On the other computer, go into the Contracts tab and then click on watch contract. Add the correct address and interface and press OK.

在别的电脑,打开以太坊钱包,去Contracts标签页面,然后点击watch contract,添加个合约地址,接口字符串,然后点击OK

Add Contract

INTERACTING WITH THE CONTRACT

跟合约交互

On the "Read from contract" you can see all the functions you can execute for free on the contract, as they are just reading information from the blockchain. Here you can see, for instance, the current "owner" of the contract (that should be the account that uploaded the contract).

Read from contract,你能看到合约中所有的,可免费执行的函数,因为他们只是从区块链读取信息。比如,在这里,你能看到合约的拥有者(部署合约的账号)。

On the "Write to contract" you have a list of all the functions that will attempt to do some computation that saves data to the blockchain, and therefore will cost ether. Select "New Proposal" and it will show all the options for that function.

Write to contract,有一个函数列表,他们会进行一些计算,并会在区块链上写入数据,因此执行这些函数,需要花费ETH. 选择"New Proposal", 会显示这个函数所有的参数。

Before interacting with the contract, you'll need to add new members so they can vote. On the "Select function" picker, choose "Add Member". Add the address of the person you want to make a member(to remove a member, pick the function "Remove Member"). On "execute from" make sure that you have the same account that is set as the owner as this is an action only the main administrator can execute. Press execute and wait a few seconds for the next block to go through with your change.

在跟合约交互前,你需要在DAO中,加入新的成员。在Select function选择器,选择“Add member”. 加入会员账号地址(要删除会员,选Remove Member函数)。在execute from,确保是Dao合约的拥有者账号,因为只有管理员才能执行这个操作。 点击execute按钮,等待几秒后,等待下个区块完成确认。

There's no list of members, but you can check if anyone is a member by putting their address on the Members function on the Read from contract column.

这没有显示成员列表,但是在Read from contract栏目,你可以在Members函数拿,输入一个账号地址来核对他们是否成员。

Also, if you want the contract to have any money of its own, you need to deposit some ether (or other token) into it, otherwise you'll have a pretty toothless organization. Press Transfer Ether & Tokens on the top right corner.

同时,如果你想让合约拥有一些钱,你需要给它冲一些ETH(或其他token),否则,那就是一个毫无用处的组织而已。点击顶部右上角的,Transfer Ether & Tokens来充值。

ADD A SIMPLE PROPOSAL: SEND ETHER

新增一个简单提议: 发送ETH

Now let's add the first proposal to the contract. On the function picker, select New Proposal.

现在我们给合约新增第一个提议。在函数选择器那,选择New Proposal.

For "beneficiary" add the address of someone you want to send ether to, and put how much you want to send in the box marked "Wei Amount." Wei is the smallest unit of ether, equal to 10^-18 ether, and must always be given as an integer. For example, if you want to send 1 ether, enter 1000000000000000000 (that's 18 zeroes). Finally, add some text describing the reason you want to do this. Leave "Transaction bytecode" blank for now. Click execute and type your password. After a few seconds the numProposals will increase to 1 and the first proposal, number 0, will appear on the left column. As you add more proposals, you can see any of them by simply putting the proposal number on the "proposals" field and you can read all about it.

在"beneficiary", 输入你想要接收ETH人的账号地址,在“Ether amount”,输入要发送的数量,按wei来算,且必须是一个整数, Wei是ETH最小的单位,1ETH = 10 18 wei,。 比如,如果你想发送1ETH, 那就输入1000000000000000000(18个0)。 最后,输入你发起这个提议的原由。 "Transaction bytecode"设为空。点击“execute”, 输入密码。 几秒后,提议的数量会增加一个,第一个提议,它的序号是0,会在左边栏目显示出来。当你加了很多提议后,你可以在“proposals”区域,简单的输入他们的编号,就可以查询到它们的信息。

Voting on a proposal is also very simple. Choose "Vote" on the function picker. Type the proposal Number in the first box and check the "Yes" box if you agree with it (or leave it blank to vote against it). Click "execute" to send your vote.

给提议投票也非常简单。 在函数选择器那,选择“Vote”,在第一个输入框,输入提议的编号,如果你同意这个提议,就选中“Yes”,否则就,不用去点选,点击“execute”来发送你的投票。

Add new proposal

When the voting time has passed, you can select "executeProposal". If the proposal was simply sending ether, then you can also leave the "transactionBytecode" field blank. After pressing "execute" but before typing your password, pay attention to the screen that appears.

当投票时间已过,你可以选择“executeProposal”。如果这个提议只是简单的发送ETH, 你可以让transactionBytecode区域为空。 点击“execute”后,在输入你的密码前,请留意屏幕上出现的东西。

If there is a warning on the "estimated fee consumption" field, then this means that for some reason the function called will not execute and will be abruptly terminated. It can mean many things, but in the context of this contract this warning will show up whenever you try to execute a contract before its deadline has passed, or if the user is trying to send a different bytecode data than the original proposal had. For security reasons if any of these things happens, the contract execution is abruptly terminated and the user that attempted the illegal transaction will lose all the ethers he sent to pay transaction fees.

如果有一个“消耗费用估算”失败警告,这就意味着这个函数不会被执行,并会被终止。有很多原因会导致这个问题,但是基于这个合约的上下文,在这个合约截止时间前,当你尝试去执行另外一个合约的时候,或者用户试图发送一个跟提议不一样的bytecode的时,会出现这个警告。出于安全考虑,如果发生这些情况,合约执行会被立即终止,用户如果试图执行这些非法交易,他们会损失掉已支付的交易费用。

If the transaction was executed, then after a few seconds you should be able to see the result: executed will turn to true and the correct amount of ether should be subtracted from this contract's balance and into the recipient address.

如果交易被正确执行,几秒后,你可以看到"executed"的结果变为:true。相应的ETH会从合约中扣除,并发送到接受者的地址上去。

ADD A COMPLEX PROPOSAL: OWN ANOTHER TOKEN

添加一个复杂提议: 拥有另外的token

You can use this democracy to execute any transaction on ethereum, as long as you can figure out the bytecode that that transaction generates. Luckily for us, you can use the wallet to do precisely that!

你可以使用这种民主,在以太坊上执行任何交易,只要你能算出这个交易生成的bytecode。好在你可以使用钱包来精确地完成这件事。

In this example, we'll use a token to show that this contract can hold more than ether and can do transactions in any other ethereum-based asset. First, create a token that belongs to one of your normal accounts. On the contract page, click Transfer Ether & Tokens to transfer some of them to your new congress contract (for simplicity, don't send more than half your coins to your DAO). After that, we are going to simulate the action you want to execute. So if you want to propose that the DAO send 500mg of a gold token to a person as a payment, then follow the steps that you'd do to execute that transaction from an account you own and press "send" but when the confirmation screens pops up, don't type your password.

在这个例子中,我们使用一个token来展示这个合约,它不仅可以持有ETH,也可以用其他基于以太坊的资产来进行交易。首先,用你的普通账号,创建一个token。 在“Contract”页面,点击“Transfer Ether & Tokens”,给Dao合约发送一些token(为了简单点,给你的Dao合约发送的token数量,不要超过50%)。完成后,我们模拟你想要执行的一些操作,如果你想提议Dao给一个人发送500mg的黄金token,来作为支付。从你的一个账号执行这个交易,点击“Send”,但是当弹出确认对话时,不要输入你的密码。

Select the bytecode

Instead, click "SHOW RAW DATA" link and copy the code displayed on the "RAW DATA" field and save it to a text file or notepad. Cancel the transaction. You'll also need the address of the contract you'll be calling for that operation, in this case the token contract. You can find it on the Contracts tab: save that somewhere too.

而是点击“SHOW RAW DATA”链接,然后复制RAW DATA区域的代码,保存到一个文本文件或notepad上。取消这个交易。你还需要一个合约地址,用它来执行这个操作,在这里,就是这个token的合约地址。你可以在“Contracts”标签找到它,并把它保存起来。

Now go back to the congress contract and create a new proposal with these parameters:

  • As the beneficiary, put the address of your token (pay attention if it's the same icon)
  • Leave Ether amount blank
  • On the Job description just write a small description on what you want to accomplish
  • On the Transaction Bytecode, paste the bytecode you saved from the data field on the previous step

现在回到DAO合约,新增一个提议:

  • beneficiary,填你的token合约地址(注意它的icon是否一致)
  • Ether amount ,留空白
  • Job description,写一个简短描叙:你想做什么
  • Transaction Bytecode,粘贴前一步保存的bytecode。
New proposal

In a few seconds you should be able to see the details on the proposal. You'll notice that the transaction bytecode won't be shown there and instead there's only a "transaction hash". Unlike the other fields, Bytecode can be extremely lengthy and therefore expensive to store on the blockchain, so instead of archiving it, the person executing the call later will provide the bytecode.

几秒后,你应该会看到该提议的详细信息。你会注意到,交易的bytecode并没有显示,而是一个交易哈希。跟其他区域不同,Bytecode可以很长,因此存储在区块链上会很费钱,因此,并没用对bytecode进行归档,而是在以后,由执行这个调用的人,提供这些bytecode。

But that, of course, creates a security hole: how can a proposal be voted without the actual code being there? And what prevents a user from executing a different code after the proposal has been voted on? That's where transaction hash comes in. Scroll a bit on the "read from contract" function list and you'll see a proposal checker function, where anyone can put all the function parameters and check if they match the one being voted on. This also guarantees that proposals don't get executed unless the hash of the bytecode matches exactly the one on the provided code.

但是,这会导致一个安全漏洞: 在没有实际代码时,如何让一个提议可以被投票?在投票开启后,又有什么办法防止用户执行不同的代码?这就引入了交易哈希。在“read from contract”函数列表,往下滚动一点,会看到一个提议核对器函数,任何人可以输入函数所有的参数,来核对是否跟正在投票的提议匹配。这就保证提议不会被执行,除非bytecode的哈希值跟提供的哈希值是一样的。

It's an older code, but it checks out

Anyone can actually check the proposal very easily by following the same steps to get the correct bytecode and then adding the proposal number and other parameters to the function called Check proposal code on the bottom of Read from contract.

任何人可以非常容易地核对提议。通过使用同一种步骤获取正确的bytecode,提议的编号,以及其他参数,然后输入到 Read from contract区域底部的 Check proposal code函数就可以实现。

The rest of the voting process remains the same: all members can vote and after the deadline, someone can execute the proposal. The only difference is that this time you'll have to provide the same bytecode you've submitted before. Pay attention to any warnings on the confirmation window: if it says it won't execute your code, check to see if the deadline has already passed, if there are enough votes and if your transaction bytecode checks out.

后面的投票流程是一样的: 所有的成员能投票,过了截止时间,一些人可以执行这个提议。唯一的不同在于,你需要提供相同的bytecode。要留意确定对话窗口的警告:如果提示说不能执行你的代码,要检查下是否已经过了截止时间,是否有足够的票数,是否你的交易bytecode检验通过。

MAKE IT BETTER

Here are some drawbacks of this current DAO that we leave as an exercise to the reader:

  • Can you make the member list public and indexed?
  • Can you allow members to change their votes (after votes are cast but before the votes are tallied up)?
  • Currently the vote message is only visible on logs, can you make a function that will display all votes?

优化

当前的DAO有一些缺点,作为练习留给读者来做:

  • 你能让成员列表公开,并能被索引吗?
  • 你能允许成员修改他们的投票吗(在投完后,提议结算前)?
  • 当前的投票消息只能在日志中看到,你能写一个函数来显示所有的投票消息吗?

The Shareholder Association

股东协会

In the previous section we created a contract that works like an invitation-only club, where members are invited or banned by the whim of the president. But this has a few drawbacks: what if someone wants to change his main address? What if some members have more weight than others? What if you actually want to trade or sell memberships or shares on an open market? What if you wanted your organization to have work as a constant decision machine by shareholders?

在前面的章节,我们创建的合约,看起来像一个邀请俱乐部,董事可以随时邀请或禁止成员。但是这有一些缺点: 如果一些人想改他们的地址怎么办? 如果一些成员有更大的权重怎么办? 如果你想在公开市场上,买卖你成员关系,股份怎么办? 如果你想让你的组织,通过股东,像一个恒定的决策机器一样工作怎么办?

We are going to modify a bit our contract to connect it to a specific token, which will work as the holding shares of the contract. First we need to create this token: go to the token tutorial and create a simple token with initial supply of 100, decimals of 0 and a percentage sign (%) as a symbol. If you want to be able to trade in fractions of a percent, then increase the supply by 100x or 1000x and then add the corresponding amount of zeros as the decimals. Deploy this contract and save its address on a text file.

我们将对合约做一点修改,让它跟一个特殊的token连接起来,token起到持有这个合约股份的作用。 首先,我们需要创建这个token: 按token教程,创建一个简单的token,发行量 100份,小数位为0,符合为%。 如果你想能够在买卖的时候带有小数的百分数,可以把发行量乘以100或1000倍,然后把小数位设置成相应的个数。 部署这个合约,把它的地址保存在一个文本文件。

Now to the shareholder code:

下面是股东协会代码

pragma solidity ^0.4.16;

contract owned{
   address public owner;

   function owned() public {
     owner = msg.sender;
   }

   modifier onlyOwner {
     require(msg.sender == owner);
     _;
   }

   function transferOwnership(address newOwner) onlyOwner public {
     owner = newOwner;
   }
}

contract tokenRecipient {
    event ReceiveEther(address sender, uint amount);
    event ReceiveTokens(address _frome, uint256 _value, address _token, bytes _extraData);

    function recevieApproval(address _from, uint256 _value, address _token, bytes _extraData) public {
      Token t = Token(_token);
      require(t.transferFrom(_from, this, _value));
      emit ReceiveTokens(_frome, _value, _token, _extraData)
    };

    function () payable public {
      emit ReceiveEther(msg.sender, msg.value);
    }
}

contract Token {
    mapping (address => uint256) public balanceOf;
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
}

/**
 * The shareholder association contract itself
**/
contract Assoication is owned, tokenRecipient {

  uint public minimumQuorum;
  uint public debatingPeriodInMinutes;
  Proposal[] public proposals;
  uint public numProposals;
  Token public sharesTokenAddress;

  event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
  event Voted(uint proposalID, bool position, address voter);
  event ProposalTallied(uint proposalID, uint result, uint quorum, bool active);
  event ChangeOfRules(uint newMinimumQuorum, uint newDebatingPeriodInMinutes, address newSharesTokenAddress);

  struct Proposal {
          address recipient;
          uint amount;
          string description;
          uint minExecutionDate;
          bool executed;
          bool proposalPassed;
          uint numberOfVotes;
          bytes32 proposalHash;
          Vote[] votes;
          mapping (address => bool) voted;
      }

   struct Vote {
          bool inSupport;
          address voter;
      }

   // Modifier that allows only shareholder to vote and create new proposal
   modifier onlyShareholders {
     requrie(sharesTokenAddress.balanceOf(msg.sender) > 0);
     _;
   }

   /**
     * Constructor function
     *
     * First time setup
     */
    function Association(Token sharesAddress, uint minimumSharesToPassAVote, uint minutesForDebate) payable public {
        changeVotingRules(sharesAddress, minimumSharesToPassAVote, minutesForDebate);
    }

    /**
    * Change voting rules
    *
    * Make so that proposals need to be discussed for at least `minutesForDebate/60` hours
    * and all voters combined must own more than `minimumSharesToPassAVote` shares of token `sharesAddress` to be executed
    *
    * @param sharesAddress token address
    * @param minimumSharesToPassAVote proposal can vote only if the sum of shares held by all voters exceed this number
    * @param minutesForDebate the minimum amount of delay between when a proposal is made and when it can be executed
    */
   function changeVotingRules(Token sharesAddress, uint minimumSharesToPassAVote, uint minutesForDebate) onlyOwner public {
       sharesTokenAddress = Token(sharesAddress);
       if (minimumSharesToPassAVote == 0 ) minimumSharesToPassAVote = 1;
       minimumQuorum = minimumSharesToPassAVote;
       debatingPeriodInMinutes = minutesForDebate;
       emit ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, sharesTokenAddress);
   }

   /**
     * Add Proposal
     *
     * Propose to send `weiAmount / 1e18` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     *
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send, in wei
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposal(
        address beneficiary,
        uint weiAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyShareholders public
        returns (uint proposalID)
    {
        proposalID = proposals.length++;
        Proposal storage p = proposals[proposalID];
        p.recipient = beneficiary;
        p.amount = weiAmount;
        p.description = jobDescription;
        p.proposalHash = keccak256(beneficiary, weiAmount, transactionBytecode);
        p.minExecutionDate = now + debatingPeriodInMinutes * 1 minutes;
        p.executed = false;
        p.proposalPassed = false;
        p.numberOfVotes = 0;
        emit ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
        numProposals = proposalID+1;

        return proposalID;
    }

    /**
     * Add proposal in Ether
     *
     * Propose to send `etherAmount` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     * This is a convenience function to use if the amount to be given is in round number of ether units.
     *
     * @param beneficiary who to send the ether to
     * @param etherAmount amount of ether to send
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposalInEther(
        address beneficiary,
        uint etherAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyShareholders public
        returns (uint proposalID)
    {
        return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode);
    }

    /**
    * Check if a proposal code matches
    *
    * @param proposalNumber ID number of the proposal to query
    * @param beneficiary who to send the ether to
    * @param weiAmount amount of ether to send
    * @param transactionBytecode bytecode of transaction
    */
   function checkProposalCode(
       uint proposalNumber,
       address beneficiary,
       uint weiAmount,
       bytes transactionBytecode
   )
       constant public
       returns (bool codeChecksOut)
   {
       Proposal storage p = proposals[proposalNumber];
       return p.proposalHash == keccak256(beneficiary, weiAmount, transactionBytecode);
   }

   /**
     * Log a vote for a proposal
     *
     * Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
     *
     * @param proposalNumber number of proposal
     * @param supportsProposal either in favor or against it
     */
    function vote(
        uint proposalNumber,
        bool supportsProposal
    )
        onlyShareholders public
        returns (uint voteID)
    {
        Proposal storage p = proposals[proposalNumber];
        require(p.voted[msg.sender] != true);

        voteID = p.votes.length++;
        p.votes[voteID] = Vote({inSupport: supportsProposal, voter: msg.sender});
        p.voted[msg.sender] = true;
        p.numberOfVotes = voteID +1;
        emit Voted(proposalNumber,  supportsProposal, msg.sender);
        return voteID;
    }


    /**
     * Finish vote
     *
     * Count the votes proposal #`proposalNumber` and execute it if approved
     *
     * @param proposalNumber proposal number
     * @param transactionBytecode optional: if the transaction contained a bytecode, you need to send it
     */
    function executeProposal(uint proposalNumber, bytes transactionBytecode) public {
        Proposal storage p = proposals[proposalNumber];

        require(now > p.minExecutionDate                                             // If it is past the voting deadline
            && !p.executed                                                          // and it has not already been executed
            && p.proposalHash == keccak256(p.recipient, p.amount, transactionBytecode)); // and the supplied code matches the proposal...


        // ...then tally the results
        uint quorum = 0;
        uint yea = 0;
        uint nay = 0;

        for (uint i = 0; i <  p.votes.length; ++i) {
            Vote storage v = p.votes[i];
            uint voteWeight = sharesTokenAddress.balanceOf(v.voter);
            quorum += voteWeight;
            if (v.inSupport) {
                yea += voteWeight;
            } else {
                nay += voteWeight;
            }
        }

        require(quorum >= minimumQuorum); // Check if a minimum quorum has been reached

        if (yea > nay ) {
            // Proposal passed; execute the transaction

            p.executed = true;
            require(p.recipient.call.value(p.amount)(transactionBytecode));

            p.proposalPassed = true;
        } else {
            // Proposal failed
            p.proposalPassed = false;
        }

        // Fire Events
        emit ProposalTallied(proposalNumber, yea - nay, quorum, p.proposalPassed);
    }

}

DEPLOYMENT AND USAGE

部署和使用

The code is deployed almost exactly like the previous code, but you need to also put a shares token address which is the address of the token that will work as a share with voting rights.

代码的部署跟之前的代码基本一致,但这你还需要提供一个股份token的地址,股份token有投票权利。

Notice these lines of codes: first we describe the token contract to our new contract. Since it only uses the balanceOf function, we only need to add that single line.

注意这行代码: 在新的合约里面,我们描述了这个token合约,因为在新的合约里面,我只用到balanceOf函数,所以我们只需加这一行就可以。

contract Token { mapping (address => uint256) public balanceOf; }

Then we define a variable of the type token, meaning that it will inherit all the functions we described earlier. Finally we point the token variable to an address on the blockchain, so it can use that and request live information. This is the simplest way to make one contract understand the other in ethereum.

然后,我们在新的合约里面,定义一个token类型的变量,这就是这变量会继承token所有的函数。最后需要把这个变量指向区块链上的一个地址,这样这个变量就能使用它来获取在线信息。这是以太坊上,最简单的方式实现合约之间的通信。

contract Association {
    token public sharesTokenAddress;
// ...
function Association(token sharesAddress, uint minimumSharesForVoting, uint minutesForDebate) {
        sharesTokenAddress = token(sharesAddress);

This association presents a challenge that the previous congress didn't have: since anyone with tokens can vote and the balances can change very quickly, the actual score of the proposal can't be counted when the shareholder votes, otherwise someone would be able to vote multiple times by simply sending his share to different addresses. So in this contract only the vote position is recorded and then the real score is tallied up on the execute proposal phase.

这个association合约会碰到一个congress合约没有的问题: 因为任何人持有这个token的人都能投票,且他们的余额能很快的变动,这样了导致当股东投票时,提议的分数无法计算,否则有些人通过把他的股份发送不同的地址,就可以进行多次投票。因此在这个合约,只能记录投票立场情况,然后在execute proposal阶段,在对真实的分数进行统计。

uint quorum = 0;
uint yea = 0;
uint nay = 0;

for (uint i = 0; i < p.votes.length, ++i) {
  Vote v = p.votes[i];
  uint voteWeight = sharesTokenAddress.balanceOf(v.voter);
  quorum += voteWeight;
  if (v.inSupport) 
  {
    yea += voteWeight;
  }
  else
  {
    nay += voteWeight;
  }
}

Another way to count the weighted votes would be to create a single signed integer to keep score of the votes and check if it was positive or negative at the end, but you'd have to convert the unsigned integer voteWeight into a signed integer using int score = int(voteWeight);

可以用另外一个方法来统计投票权重,创建一个有符号整数来保存投票分数,在最后的时候,在检查是正还是负,但是你必须使用 int sorce = int(voteWeight) 把非符号的整数voteWeight转成有符合的整数。

Using this DAO is exactly like before: members create new proposals, vote on them, wait until the deadline passes and then anyone can count the votes and execute it.

跟之前一样用DAO: 成员创建新的提议,给提议投票,等到过了截止时间,然后任何人能计算票数,再执行提议。

Association example

BUT HOW CAN I LIMIT THE OWNER'S POWER?

如何限制拥有者的权利?

On this contract the address set as owner has some special powers: they can add or ban members at will, change the margin needed for a win, alter the time required for debate and the quorum necessary for the vote to pass. But this can be solved by using yet another power the owner has: to change ownership.

在这个合约,被设置为owner的地址有一些特殊的权利: 他们能按自己意愿新增,禁止成员,为了获胜,修改所需的额外票数,改变辩论时间,通过提议所需的法定人数量。但是可以使用另外一个权力来解决这个问题: 修改合约的拥有权。

The owner could change the ownership to no one by pointing the new owner as 0x00000.... This would guarantee that the rules will never change, but it's an irreversible action. The owner can also change the ownership to the contract itself: just click on "copy address" and add it on the "new owner" field. This would make that all the powers of the owner could be executed by creating proposals.

拥有者可以把拥有权转移给0x0000....。这可以保证所有的规则永远无法更改,但是这是一个不可逆操作。拥有者也可以把所有权转移给合约自己: 只需点击“copy address”,然后把它加入到"new owner"输入框。这可以通过创建提议来行使拥有者所有的权利。

If you want, you can also set one contract as the owner of the other: suppose you wanted a corporate structure where you wanted a President for life that had the power to appoint board members, which could then issue more shares and finally these shares voted on how to spend a budget. You could create an Association contract, that used a mintable token owned by a congress finally owned by a single account.

如果你愿意,你也可以把一个合约设置成另外一个合约的拥有者: 假设你想一个这么一个公司架构: 你需要一个终身总裁,它有权利任命董事成员,它可以发行更多的股份,最后通过这些股份来投票决定怎么花预算。你可以创建一个Association合约,它使用以个mintable token合约,而这个mintable token合约被一个congress合约拥有。最后只有一个账号拥有这个Association合约。

But what if you wanted different rules for voting? Maybe to change voting rules you'd need a 80% consensus, or maybe the members are different. In that case, you can create another identical DAO or use some other source code and plug that one as the owner of the first.

但如果你想要不同的投票规则怎么办? 或许要想修改规则,你需要80%的共识,或者这些成员是不同的。在这里,你可以创建另外一个相同的DAO或使用相同的源码,把这个设置成第一个DAO的拥有者。

Liquid democracy

流动的民主

Voting on all expenses and actions of a contract takes time and requires users to be constantly active, informed and attentive. Another interesting approach is to elect an appointed account that will have control over a contract and then be able to take swift decisions over it.

投票需要花钱,以及执行合约上需要发时间,且要求用户用常常处于活跃状态,注意通知信息,花精力关注投票。 一个有趣的办法,就是选出一个指定的账号,让她控制这个合约,然后它可以快速做决策。

We are going to implement a version of what's usually called Liquid Democracy, which is a more flexible delegative democracy. In this kind of democracy, any voter can be a potential delegate: instead of voting the candidate you want, you just say which voter you trust to handle this decision for you. Your voting weight is delegated to them and they can in turn delegate it to another voter they trust and so on. The end result should be that the most voted account is one that has trust connections to the largest amount of voters.

我们将会实现一个通常被称之为“流动民主”的版本, 它是一个更加灵活的代表民主。在这种民主中,任何投票者都有可能成为代表。取代你给候选人投票的方式,你只需要选一个你信任的投票者帮你来做决定。你把你的投票权重委托给他们,他们可以轮流,委托另外一个他们信任的投票者,以此类推。 最终结果是获得最多选票的账号跟最多数量的投票者建立起信任关系。

THE CODE

代码

pragma solidity ^0.4.16;

contract token {
  mapping (address => uint256) public balanceOf;
}

contract LiquidDemocracy  {
  token public votingToken;
  bool underExecution;
  address public appointee;
  mapping (address => uint) public voterId;
  mapping (address => uint256) public voteWeight;

  uint public delegatedPercent;
  uint public lastWeightCalculation;
  uint public numberOfDelegationRounds;

  uint public numberOfVotes;
  DelegatedVote[] public delegatedVotes;
  string public forbiddenFunction;


  struct DelegatedVote {
    address nominee;
    address voter;
  }

  /**
   *  Construct function
  **/
  function LiquidDemocracy (
      address votingWeightToken,
      string forbiddenFunctionCall,
      uint percentLossInEachRound
    ) public {
      votingToken = token(votingWeightToken);
      delegatedVotes.length++;
      delegatedVotes[0] = DelegatedVote({nominee: 0, voter: 0});
      forbiddenFunction = forbiddenFunctionCall;
      delegatedPercent = 100 - percentLossInEachRound;
      if (delegatedPercent > 100) delegatedPercent = 100;
    }

    /**
     * Vote for an address
     *
     * Send your vote weight to another address
     *
     * @param nominatedAddress The destination address receiving the sender's vote
    **/
    function vote(address nominatedAddress) public returns (uint voteIndex) {
       if (voterId[msg.sender] == 0) {
         voterId[msg.sender] = delegatedVotes.length;
         numberOfVotes++;
         voteIndex = delegatedVotes.length++;
         numberOfVotes = voteIndex;
       }
       else {
         voteIndex = voterId[msg.sender];
       }
       delegatedVotes[voteIndex] = DelegatedVote({nominee: nominatedAddress, voter: msg.sender});
       return voteIndex;
    }

    /**
     *  Perform Executive Action
     *
     * @param target The destination address to interact with
     * @param valueInWei The amount of ether to send along with the transaction
     * @param bytecode The data bytecode for the transaction
    **/
    function execute(address target, uint valueInWei, bytes32 bytecode) public {
      require(msg.sender == appointee                           // If caller is the current appointee,
          && !underExecution                                    // if the call is being executed,
          && bytes4(bytecode) != bytes4(keccak256(forbiddenFunction)) //and it's not trying to do the forbidden function
          && numberOfDelegationRounds >= 4);                     //and delegation has been calculated enough

      underExecution = true;
      assert(target.call.value(valueInWei)(bytecode)); // Then execute the command
      underExecution = false;
    }

    /**
     * Calculate Votes
     *
     * Go through all the delegated vote logs and tally up each address's total rank
    **/
    function caculateVotes()  public returns (address winner){
      address currentWinner = appointee;
      uint currentMax = 0;
      uint weight = 0;
      DelegatedVote storage v = delegatedVotes[0];
      if ( now  > lastWeightCalculation + 90 minutes)
      {
        numberOfDelegationRounds = 0;
        lastWeightCalculation = now;

        // Distribute the initial weight
        for (uint i = 1; i < delegatedVotes.length; i++)
        {
            voteWeight[delegatedVotes[i].nominee] = 0;
        }
        for ( i = 1; i < delegatedVotes.length; i++)
        {
            voteWeight[delegatedVotes[i].voter] = votingToken.balanceOf(delegatedVotes[i].voter);
        }
      }
      else
      {
          numberOfDelegationRounds++;
          uint lossRatio = 100 * delegatedPercent ** numberOfDelegationRounds / 100 ** numberOfDelegationRounds;
          if (lossRatio > 0)
          {
            for (i = 1; i < delegatedVotes.length; i++)
            {
                v = delegatedVotes[i];

                if ( v.nominee != v.voter && voteWeight[v.voter] > 0)
                {
                    weight = voteWeight[v.voter] * lossRatio / 100;
                    voteWeight[v.voter] -= weight;
                    voteWeight[v.nominee] += weight;
                }

                if (numberOfDelegationRounds > 3 && voteWeight[v.nominee] > currentMax)
                {
                    currentWinner = v.nominee;
                    currentMax = voteWeight[v.nominee];
                }
            }
          }
      }

      return currentWinner;
    }
}

DEPLOYMENT

部署

First, you need a token. If you have followed the Shareholder association tutorial above, you can use the same token as you had previously, otherwise just deploy a new token and distribute it among some accounts. Copy the token address.

Deploy the democracy contract, and put the token address on the Voting weight token, put 75 as the Percent loss in each roundand transferOwnership(address) (without any spaces or extra characters!) as the forbidden function.

首先,你需要一个token,如果你之前按照Shareholder association教程创建过一个token,那就用这样token,否则,就部署一个新的token合约到同一个账号。复制token地址。

部署民主合约,同时在Voting weight token填token合约地址,在Percent loss in each round填75, 在把transferOwnership(address) 设为forbidden function.

SELECTING A DELEGATE

选择代表

Now deploy the Liquid democracy and go to its page. First have any of the shareholders vote on who they would trust to make decisions on behalf of this contract. You can vote on yourself if you want to be the final decision maker, or on the zero address, if you'd rather have no one representing you on that role.

现在部署流动民主合约,然后去这个合约页面。首先选出他们信任的人来代表他们,在这个合约上做决定。如果你自己想成为最终的决策者,也可以给自己投票,或者如果你想弃权,就把代表的地址出填上0。

After enough people have cast their votes, you can execute the function Calculate Votes so it will calculate everyone's voting weight. This function needs to be run multiple times, so the first run it will just set everyone's weight as their balance in the selected token, in the next round that voting weight will go to the person you voted appointed, in the next it will go to the person voted by the person you chose and so on. To prevent infinite loops of vote delegations, each time a vote is forwarded it loses a bit of power, set by at contract launch at percentLossInEachRound. So if the loss is set at 75%, it means that the person you vote gets 100% of your weight, but if they delegate the vote to someone else only 75% of their weight is forwarded. That person can delegate to someone else but they'll get only 56% of your voting weight and so on. If the ratio is anything lower than 100% there will be a finite moment where recalculating voting delegation won't change the result anymore, but if it's a 100% it means that voting weights will simply circulate around any potential loops.

有足够多的人投完票后,你可以执行Calculate Votes函数,这样它就会计算出每个人的投票权重。这个函数需要运行多次,第一次运行,只是把每个人的权重按token的余额来计算,在下一轮,投票权重会转移给你指定的候选人,再在下一轮,这个权重会给到你选定的候选人所选定的候选人,以此类推。这防止投票委托无限循环,每进行一论投票,每个投票人就会失去一点权力,这个是在合约启动时,用percentLossInEachRound来设定。因此如果丢失率设置为75%, 它意味着你投票的人将会获取你100%的投票权重,但是如果他们把投票委托给其他人, 他们75%的投票权重会转移给别人。那这些人只能获得你的56% (75%*%75% = 56.25%)的投票权重,以此类推。 如果这个比率低于100%,在有限的时间内,会不断的重新计算投票委托,直到再也无法改变结果,但是如果比率是100%,那就意味这个投票权重会计算潜在可能的循环。

If there has been more than one hour and a half since this round of calling Calculate votes has started, all weights will reset and will be recalculated based on the original token balance, so if you have recently received more tokens you should execute this function again.

如果从调用 Calculate votes 算起,经过了多达1个半小时,所有的权重会被重置,并重新基于初始token的余额来重新计算,因此如果最近你收到了更多的token,你应再执行一次这个函数。

HOUSE OF REPRESENTATIVES

众议院

What is all that vote delegation good for? For one, you can use it instead of the token weight on an Association. First of all, get the code for a shareholder association but replace the first lines where it describes the token:

投票委托有哪些好处? 一个是,在Association合约中,你可以用它了替代token权重。 首先,获得一份shareholder association合约代码,把里面描述token的合约,如下:

contract Token {
    mapping (address => uint256) public balanceOf;
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
}

替换成:

contract Token {
   mapping (address => uint256) public voteWeight;
   uint public numberOfDelegationRounds;

   function balanceOf(address member) constant returns (uint256 balance){
      if (numberOfDelegationRounds < 3)
        return 0;
      else
        return this.voteWeight(member);
   }
   
   function transferFrom(address _from, address _to, uint256 _value) return (bool success);
} 

When you are writing your contract you can describe multiple other contracts used by your main contract. Some might be functions and variables that are already defined on the target contract, like voteWeight and numberOfDelegationRounds. But notice that balanceOf is a new function, that doesn't exist neither on the Liquid Democracy or the Association contract, we are defining it now, as a function that will return the voteWeight if at least three rounds of delegations have been calculated.

当你写多个合约时,你可以的主合约可以使用多个其他合约。有一些函数,变量可能在目标合约中已经定义了,比如voteWeightnumberOfDelegationRounds。但要注意,balanceOf是一个新函数,它即不存在Liquid Democracy合约里面,也不在Association合约里面,我们现在在token合约里面定义了这个函数,当至少经过三轮委托计算后,才会返回voteWeight

Use the Liquid democracy as the Token Address instead of the original token and proceed to deploy the shareholder association as usual. Just like before, users can create new proposals on what to do or cast votes on these issues, but now, instead of using the token balance as the voting power we are using a delegative process. So if you are a token holder, instead of having to keep yourself constantly informed by all the issues, you can just select someone you trust and appoint them, and then they can choose someone they trust: the result is that your representative, instead of being limited to a given arbitrary geographical proximity, will be someone in your social proximity.

Liquid democracy 当做Token地址,来替换原始的Token,然后跟平常一样,进行部署shareholder association合约。跟之前一样,用户可以创建新的提议,并投票,但是现在,不用token余额多少来代表投票权利大小,而是使用一个委托流程。所以如果你token持有者,你不再需要自己时刻关注所有相关事务,你只需要选择一些你信任的让你,然后委托他们帮你做决定,同时他们也可以选择他们所信任的人:结果将会是,你的代表不再被局限跟你地理位置相近的人群中,他们可以会来自,跟你的社会属性相近的人群中。

Also it means that you can switch your vote at any moment: if your representative has voted against your interests in some issue you can, before the proposal votes are tallied up, switch your appointee, or just choose to represent yourself on the issue and cast the vote yourself.

这也就意味着,你可以随时改变你的投票: 如果你的代表投票反对一些你感兴趣的提议,在提议结束前,你可以改变你的候选人,或你可以自己来投票。

THE EXECUTIVE BRANCH

行政部门

Delegative democracies are a great way to choose representatives, but voting on individual proposals might be too slow for some important or simpler decisions: that's why most democratic governments have an executive branch, where an appointed person has the right to represent the state.

委任式民主是一种非棒的选举代表方式,但是对于一些重要的,简单的决策,在个别提议上,进行投票可能非常慢。这就是为什么很多民主政府有一个行政部门,这个部门有一个委托人,他有权利代表这个州。

After four rounds of delegations, the address with more weight will be set as the Appointee. If there are many delegated votes, then a few more rounds of Calculate Votes might be necessary to settle in the final appointed address.

经过4论委托后,拥有更多的权重的地址会被设置成任命者。如果有很多的委托投票,可能会需要更多论的Calculate Votes来算出最终的任命地址。

The Appointee is the only address that can call the Execute function, which will be able to execute (almost) any function representing the democracy as a whole. If there is any ether or token stored in the Liquid democracy contract, the Appointee will be allowed to move it anywhere.

任命者是唯一能执行Execute函数的地址,它几乎能够代表整个民主组织执行任何函数。如果Liquid democracy合约里面有ETH或其他token,任命者会被允许把他们转移到任何地方。

If you have followed our example and created a Shareholder association using this liquid democracy as a token, then you should be able to use the executive branch in an interesting manner: go to the main Association address and execute a Transfer Ownershipfunction to the liquid democracy.

如果你已经按照我们的例子,创建了一个 Shareholder association合约, 并把这个liquid democracy 合约当做一个token, 然后你可以用一个有趣的方式来使用这个行政部门: 去主Association合约页面,并执行Transfer Ownershipf函数, 把Association的拥有者转移给这个liquid democracy合约。

Once that transfer is complete, switch the function to Change Voting Rules. This allows you to change some essential voting rules, like the minimum quorum needed for a vote to pass or the time a new proposal needs to stay on the floor. Try changing these settings and click execute: when the confirmation window pops up it will tell you that the transaction Can't be executed. This happens, of course, because only the address set as Owner can change these settings and the contract will reject this transaction attempt. So instead of typing your password copy the code on the data field and save it to a text file. Click cancel, scroll to the top and click copy address and also save that to a text file.

一旦完成转移,切换到Change Voting Rules函数。它可以允许你修改一些重要的投票规则,比如最少投票人数,提议截止时间。试着改变这些设置,点击execute: 当确认窗口弹出时,它会告诉你这个交易不能被执行。这个肯定会发生,因为只有Shareholder association合约的Owner才能更改这些设置,所以这个合约会拒绝这个交易。因此,不要输入你的密码, 而是复制SHOW RAW DATA中的数据,把它保存到一个文本文件。 点击Cancel, 滚动到页面到顶部,点击copy address, 然后把地址保存到一个文本文件。

Now go to the Liquid democracy page and choose execute. On target put the address of the association contract, leave ether amount at 0 and paste the code you copied previously into the bytecode data field. Make sure you are executing it from the account set as the appointee and click execute.

现在我去Liquid democracy合约页面,选择execute, 在target处,输入Shareholders association合约的地址,让 ether amount处保持为0, 粘贴之前复制的数据(执行Change Voting Rules交易时的raw data)到bytecode data输入框。 确保执行这个函数的账号是任命者,然后点击execute

Once the transaction has been picked up, the Liquid democracy will pass the order to the association and the new voting rules might apply. The appointee has the absolute power to do anything that the Liquid democracy contract can execute. You can use the same technique to create a Mintable Token owned by the delegative democracy, and then allow the appointee to mint tokens or freeze accounts.

一旦交易被处理, Liquid democracy 合约会给association合约传递修改投票规则命令,新的投票规则将会被采纳。任命者拥有绝对的权利来做任何在Liquid democracy合约中可以做的事情。你也可以使用同样的技术来创建一个Mintable Token,让后把拥有者设置成Liquid democracy。 然后允许任命者来发币或冻结账号。

To prevent abuses of powers, you can set one Forbidden function that the Appointee cannot ever do. If you followed our example the forbidden function is the transferOwnership(address), to prevent the appointee from transferring the ownership of the association to themselves (in politics, when a president uses his executive power to transfer to themselves something that used to belongs to the presidency, it's a coup or embezzling).

为防止权利滥用,你可以设置一个Forbidden function,甚至任命者也不可以调用这个函数。如过你做过我们的例子,这个禁止函数是:transferOwnership(address), 它防止任命者把association合约的拥有者关系转移给他们自己(在政治上,当一个总统使用它的行政权利把一些属于总统职位的东西转移给他们自己, 这叫做政变或贪污)。

Time-Locked Multisig

时间锁定多重签名

Sometimes time can also be used as a great security mechanism. The following code is based on the congress DAO but with a different twist. Instead of every action requiring the approval of an X number of members, instead any transactions can be initiated by a single member, but they all will require a minimum amount of delay before they can be executed, which varies according to the support that transaction has. The more approvals a proposal has, the sooner it can be executed. A member can vote against a transaction, which will mean that it will cancel one of the other approved signatures.

有时候,时间也可以作为一种非常好的安全机制。下面的代码是基于congress Dao合约,但是有一个不同的地方。 对于每个动作需要X个成员的批准的方式,取而代之,任何交易可以被单个成员发起,但是交易在被执行之前,需要一个最少的时间延迟,这延迟时间取决于支持这个交易的人数。认可提议的人越多,它就会越早的被执行。成员可以给交易投反对票,这就意味着它会取消另外一个人的认可签名。

This means that if you don't have urgency, one or two signatures might be all you need to execute any transaction. But if a single key is compromised, other keys can delay that transaction for months or year or even stop it from being executed.

这意味着,如果你没有紧急的事情,要执行一个任何一个交易,你或许只需要1,2个签名就可以。但是如果一个密钥不签名支持,那么其他的密钥就您给把这个交易延迟多个月或上年,甚至阻止交易被执行。

HOW IT WORKS

它是如何工作的

A transaction that has been approved by all keys can be executed after ten minutes (this amount is configurable), and the amount of time it requires doubles every time for every 5% of members who don't vote (and quadruples if they actively vote against). If it's a simple ether transaction, the transaction is executed as soon as a vote of support puts it under the required time, but a more complex transaction will require it to be manually executed with the correct bytecode. These are the default values, but this can be set differently when creating the contract:

一个交易被所有的密钥认可,10分钟后(这个时间是可以配置的),它便可被执行。所需要的时间,随着每%5的成员投反对票(如果他们经常的投反对票,那就4倍),会翻倍。如果它是一个简单的ETH交易,只要到了所需时间,交易就会被执行。但是一个更为复杂的交易需要使用正确的bytecode,来手动执行。下面是一些默认的时间,但是在创建合约的时,可以设置不同的值。

Number of members approving transaction: Approximate time delay

赞成交易成员数: 近似延迟时间

  • 100% approval: 10 minutes (minimum default)
  • 90% approval: 40 minutes
  • 80%: 2hr 40min
  • 50%: about a week
  • 40%: 1 month
  • 30%: 4 months
  • 20%: Over a year
  • 10% or less: 5 years or never

Once the minimum amount of time has passed, anyone can execute the transaction (See "Congress" for a more complete walktrough). This is intentional, as it allows someone to schedule a transaction or hire someone else to execute it.

一旦经过最少时间,任何人能执行这个交易(更为完整的内容,请查看"Congress")。这是故意这样设计的,因为它允许一些人安排一个交易或雇别人来执行交易。

THE CODE

pragma solidity ^0.4.16;

contract owned {
    address public owner;

    function owned() public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) onlyOwner public {
        owner = newOwner;
    }
}

contract tokenRecipient {
    event ReceivedEther(address sender, uint amount);
    event ReceivedTokens(address _from, uint256 _value, address _token, bytes _extraData);

    function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public {
        Token t = Token(_token);
        require(t.transferFrom(_from, this, _value));
        emit ReceivedTokens(_from, _value, _token, _extraData);
    }

    function () payable  public {
        emit ReceivedEther(msg.sender, msg.value);
    }
}

interface Token {
    function transferFrom(address _from, address _to, uint256 _value)   external returns  (bool success);
}

contract TimeLockMultisig is owned, tokenRecipient {

    Proposal[] public proposals;
    uint public numProposals;
    mapping (address => uint) public memberId;
    Member[] public members;
    uint minimumTime = 10;

    event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
    event Voted(uint proposalID, bool position, address voter, string justification);
    event ProposalExecuted(uint proposalID, int result, uint deadline);
    event MembershipChanged(address member, bool isMember);

    struct Proposal {
        address recipient;
        uint amount;
        string description;
        bool executed;
        int currentResult;
        bytes32 proposalHash;
        uint creationDate;
        Vote[] votes;
        mapping (address => bool) voted;
    }

    struct Member {
        address member;
        string name;
        uint memberSince;
    }

    struct Vote {
        bool inSupport;
        address voter;
        string justification;
    }

    // Modifier that allows only shareholders to vote and create new proposals
    modifier onlyMembers {
        require(memberId[msg.sender] != 0);
        _;
    }

    /**
     * Constructor function
     *
     * First time setup
     */
    function TimeLockMultisig(address founder, address[] initialMembers, uint minimumAmountOfMinutes)  public payable {
        if (founder != 0) owner = founder;
        if (minimumAmountOfMinutes !=0) minimumTime = minimumAmountOfMinutes;
        // It’s necessary to add an empty first member
        addMember(0, '');
        // and let's add the founder, to save a step later
        addMember(owner, 'founder');
        changeMembers(initialMembers, true);
    }

    /**
     * Add member
     *
     * @param targetMember address to add as a member
     * @param memberName label to give this member address
     */
    function addMember(address targetMember, string memberName) onlyOwner  public {
        uint id;
        if (memberId[targetMember] == 0) {
            memberId[targetMember] = members.length;
            id = members.length++;
        } else {
            id = memberId[targetMember];
        }

        members[id] = Member({member: targetMember, memberSince: now, name: memberName});
        emit MembershipChanged(targetMember, true);
    }

    /**
     * Remove member
     *
     * @param targetMember the member to remove
     */
    function removeMember(address targetMember) onlyOwner  public {
        require(memberId[targetMember] != 0);

        for (uint i = memberId[targetMember]; i<members.length-1; i++){
            members[i] = members[i+1];
        }
        delete members[members.length-1];
        members.length--;
    }

    /**
     * Edit existing members
     *
     * @param newMembers array of addresses to update
     * @param canVote new voting value that all the values should be set to
     */
    function changeMembers(address[] newMembers, bool canVote)  public {
        for (uint i = 0; i < newMembers.length; i++) {
            if (canVote)
                addMember(newMembers[i], '');
            else
                removeMember(newMembers[i]);
        }
    }

    /**
     * Add Proposal
     *
     * Propose to send `weiAmount / 1e18` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     *
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send, in wei
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposal(
        address beneficiary,
        uint weiAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyMembers public 
        returns (uint proposalID)
    {
        proposalID = proposals.length++;
        Proposal storage p = proposals[proposalID];
        p.recipient = beneficiary;
        p.amount = weiAmount;
        p.description = jobDescription;
        p.proposalHash = keccak256(beneficiary, weiAmount, transactionBytecode);
        p.executed = false;
        p.creationDate = now;
        emit ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
        numProposals = proposalID+1;
        vote(proposalID, true, '');

        return proposalID;
    }

    /**
     * Add proposal in Ether
     *
     * Propose to send `etherAmount` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     * This is a convenience function to use if the amount to be given is in round number of ether units.
     *
     * @param beneficiary who to send the ether to
     * @param etherAmount amount of ether to send
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposalInEther(
        address beneficiary,
        uint etherAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyMembers public 
        returns (uint proposalID)
    {
        return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode);
    }

    /**
     * Check if a proposal code matches
     *
     * @param proposalNumber ID number of the proposal to query
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send
     * @param transactionBytecode bytecode of transaction
     */
    function checkProposalCode(
        uint proposalNumber,
        address beneficiary,
        uint weiAmount,
        bytes transactionBytecode
    )
        constant public 
        returns (bool codeChecksOut)
    {
        Proposal storage p = proposals[proposalNumber];
        return p.proposalHash == keccak256(beneficiary, weiAmount, transactionBytecode);
    }

    /**
     * Log a vote for a proposal
     *
     * Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
     *
     * @param proposalNumber number of proposal
     * @param supportsProposal either in favor or against it
     * @param justificationText optional justification text
     */
    function vote(
        uint proposalNumber,
        bool supportsProposal,
        string justificationText
    )
        onlyMembers public 
    {
        Proposal storage p = proposals[proposalNumber]; // Get the proposal
        require(p.voted[msg.sender] != true);           // If has already voted, cancel
        p.voted[msg.sender] = true;                     // Set this voter as having voted
        if (supportsProposal) {                         // If they support the proposal
            p.currentResult++;                          // Increase score
        } else {                                        // If they don't
            p.currentResult--;                          // Decrease the score
        }

        // Create a log of this event
      emit Voted(proposalNumber,  supportsProposal, msg.sender, justificationText);

        // If you can execute it now, do it
        if ( now > proposalDeadline(proposalNumber)
            && p.currentResult > 0
            && p.proposalHash == keccak256(p.recipient, p.amount, '')
            && supportsProposal) {
            executeProposal(proposalNumber, '');
        }
    }

    function proposalDeadline(uint proposalNumber) constant  public returns(uint deadline)  {
        Proposal storage p = proposals[proposalNumber];
        uint factor = calculateFactor(uint(p.currentResult), (members.length - 1));
        return p.creationDate + uint(factor * minimumTime *  1 minutes);
    }

    function calculateFactor(uint a, uint b)  internal pure returns (uint factor) {
        return 2**(20 - (20 * a)/b);
    }

    /**
     * Finish vote
     *
     * Count the votes proposal #`proposalNumber` and execute it if approved
     *
     * @param proposalNumber proposal number
     * @param transactionBytecode optional: if the transaction contained a bytecode, you need to send it
     */
    function executeProposal(uint proposalNumber, bytes transactionBytecode)  public {
        Proposal storage p = proposals[proposalNumber];

        require(now >= proposalDeadline(proposalNumber)                                         // If it is past the voting deadline
            && p.currentResult > 0                                                              // and a minimum quorum has been reached
            && !p.executed                                                                      // and it is not currently being executed
            && checkProposalCode(proposalNumber, p.recipient, p.amount, transactionBytecode));  // and the supplied code matches the proposal...


        p.executed = true;
        assert(p.recipient.call.value(p.amount)(transactionBytecode));

        // Fire Events
        emit ProposalExecuted(proposalNumber, p.currentResult, proposalDeadline(proposalNumber));
    }
}

DEPLOYMENT AND USAGE

部署和使用

Deploy that code as you have done before on these tutorials. On the deployment parameters, leaving the minimum time blank will default to 30 minutes, if you want faster lock times, then put 1 minute. After uploading, execute the functions "Add Members" to add new members of your group, they can be either other people you know or accounts on different computers or stored offline.

在开始本教程前,跟之前一样部署好代码。在设置参数时, 把最少时间设置30分钟,如果你想更快点,可以设置为1分钟。完成上传后,执行Add Members函数,加一些新的成员到组里,他们可以是一些你知道的人,或者不同计算机上的账号,或者是离线账号。

The account set as "owner" is very powerful as it can add or remove members at will. Therefore, after you added the main members, we recommend that you set the "owner" to another account, by executing the function Transfer Membership. Set that to the multisig itself if you want to have all additions or removals of members to be voted, just like any other transaction. Another alternative is to set that to another trusted multisig wallet, or maybe to 0x000 if you want the number of members to be fixed forever. Remember, the funds on this contract are only as safe as the the "owner" account.

被设置“owner”的账号它可以自由的增加,删减成员,它的权利非常大。因此,等你添加完主要的成员后,我们建议你通过执行Transfer Membership,把“owner”设置为别的账号。如果你想通过投票来决定增加,删除成员,就跟其他任何交易一样,那么你可以把owner设置为multisig合约自己。另外一种办法就是把owner设置成一个多重签名的钱包。 如果你就想永远不再加减成员,就设置成0x000。记住,这个合约里面的资金跟”owner“账号同样安全。

As with any of the above DAO's, this contract can hold ether, any ethereum based tokens and execute any contract. To do that, check how to execute complex proposals on the congress DAO.

根据上面其他DAO合约一样,这个合约也能存ETH, 任何基于以太坊的token,可以执行任何合约。 要想做这些,请查看在congress DAO上,如何执行复杂提议

CAVEATS AND IMPROVEMENTS

警告和改进

For simplicity's sake, a vote against a proposal simply counts as one less vote of support. If you want, you can play around with the idea that negative votes have more weight, but this means that a minority of members could have an effective veto power on any proposed transaction!

How else could you improve this contract?

出于简单考虑,给提议投一个反对票只是简单统计为少了一票支持。如果你愿意,你可以考虑下这个想法: 反对票的权重会更大,但这就意味着只需要一小部分成员就可以达到否决任何提议交易。

你有还有别的方法改进这个合约吗?

Let's go exploring!

You have reached the end of this tutorial, but it's just the beginning of a great adventure. Look back and see how much you accomplished: you created a living, talking robot, your own cryptocurrency, raised funds through a trustless crowdfunding and used it to kickstart your own personal democratic organization.

让我们一起探索!

你已经到了教程的结尾,但是这仅仅是伟大冒险的开始。回头看看,你完成了多少事情: 创建一个在线的,会说话的机器人,你拥有自己的加密货币,通过免信任的众筹来筹集资金,并使用它发起你个人的民主组织。

WHAT COULD HAPPEN NEXT?

  • The tokens you still control could be sold on a decentralized exchange or traded for goods and services to fund further development of the first contract and grow the organization.
  • Your DAO could own its own name on the name registrar, and then change where it's redirecting in order to update itself if the token holders approved.
  • The organization could hold not only ethers, but any other kind of coin created on ethereum, including assets whose values are tied to the bitcoin or dollar.
  • The DAO could be programmed to allow a proposal with multiple transactions, some scheduled to the future. It could also own shares of other DAOs, meaning it could vote on larger organization or be a part of a federation of DAOs.
  • The Token Contract could be reprogrammed to hold ether or to hold other tokens and distribute it to the token holders. This would link the value of the token to the value of other assets, so paying dividends could be accomplished by simply moving funds to the token address.

接下来会发生什么?

  • 你可以在去中心化交易所卖掉你的token,或用它买东西,服务。 来进一步开发第一个合约,发展组织。
  • 这个组织,不仅持有ETH, 还有可以持有任何在以太坊上创建的币,包括那些跟bitcion或者dollar锚定的资产。
  • 通过对DAO编程,来允许一个带有多个交易的提议,有些可以安排在以后执行。它也可以拥有其他DAO组织的股份,这意味着它在一个更大的组织进行投票或者成为另外DAO的一个联邦。
  • 通过对Token合约重新编程,让它可以持有ETH,或其他token,把他们分发给token的持有者。 这会把其他有价值的资产跟token的价值连接起来,这样对于支付利息,可以通过简单得把token转移到token地址就能完成。

This all means that this tiny society you created could grow, get funding from third parties, pay recurrent salaries, own any kind of crypto-assets and even use crowdsales to fund its activities. All with full transparency, complete accountability and complete immunity from any human interference. While the network lives the contracts will execute exactly the code they were created to execute, without any exception, forever.

这个就意味着,你创建的小社会可以发展,从第三方获得资金,支付工资,拥有任何类型的加密资产,甚至使用众筹来为活动筹集资金。所有的这些都是透明的,完全可问责的,完全免受人类干预。合约一直会在以太坊网络,它会精确的执行所创建的代码,永远没有异常。

So what will your contract be? Will it be a country, a company, a non-profit group? What will your code do?

That's up to you.

那么你的合约会成为什么? 成为一个国家,一个公司,一个非盈利组织? 你的代码会做什么?

这完全取决于你!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容