通过例子学习 Solidity¶

Voting¶

The following contract is quite complex, but showcasesa lot of Solidity’s features. It implements a votingcontract. Of course, the main problems of electronicvoting is how to assign voting rights to the correctpersons and how to prevent manipulation. We will notsolve all problems here, but at least we will showhow delegated voting can be done so that vote countingis automatic and completely transparent at thesame time.

The idea is to create one contract per ballot,providing a short name for each option.Then the creator of the contract who serves aschairperson will give the right to vote to eachaddress individually.

The persons behind the addresses can then chooseto either vote themselves or to delegate theirvote to a person they trust.

At the end of the voting time, winningProposal()will return the proposal with the largest numberof votes.

  1. pragma solidity ^0.4.16;
  2.  
  3. /// @title Voting with delegation.
  4. contract Ballot {
  5. // This declares a new complex type which will
  6. // be used for variables later.
  7. // It will represent a single voter.
  8. struct Voter {
  9. uint weight; // weight is accumulated by delegation
  10. bool voted; // if true, that person already voted
  11. address delegate; // person delegated to
  12. uint vote; // index of the voted proposal
  13. }
  14.  
  15. // This is a type for a single proposal.
  16. struct Proposal {
  17. bytes32 name; // short name (up to 32 bytes)
  18. uint voteCount; // number of accumulated votes
  19. }
  20.  
  21. address public chairperson;
  22.  
  23. // This declares a state variable that
  24. // stores a `Voter` struct for each possible address.
  25. mapping(address => Voter) public voters;
  26.  
  27. // A dynamically-sized array of `Proposal` structs.
  28. Proposal[] public proposals;
  29.  
  30. /// Create a new ballot to choose one of `proposalNames`.
  31. function Ballot(bytes32[] proposalNames) public {
  32. chairperson = msg.sender;
  33. voters[chairperson].weight = 1;
  34.  
  35. // For each of the provided proposal names,
  36. // create a new proposal object and add it
  37. // to the end of the array.
  38. for (uint i = 0; i < proposalNames.length; i++) {
  39. // `Proposal({...})` creates a temporary
  40. // Proposal object and `proposals.push(...)`
  41. // appends it to the end of `proposals`.
  42. proposals.push(Proposal({
  43. name: proposalNames[i],
  44. voteCount: 0
  45. }));
  46. }
  47. }
  48.  
  49. // Give `voter` the right to vote on this ballot.
  50. // May only be called by `chairperson`.
  51. function giveRightToVote(address voter) public {
  52. // If the argument of `require` evaluates to `false`,
  53. // it terminates and reverts all changes to
  54. // the state and to Ether balances. It is often
  55. // a good idea to use this if functions are
  56. // called incorrectly. But watch out, this
  57. // will currently also consume all provided gas
  58. // (this is planned to change in the future).
  59. require(
  60. (msg.sender == chairperson) &&
  61. !voters[voter].voted &&
  62. (voters[voter].weight == 0)
  63. );
  64. voters[voter].weight = 1;
  65. }
  66.  
  67. /// Delegate your vote to the voter `to`.
  68. function delegate(address to) public {
  69. // assigns reference
  70. Voter storage sender = voters[msg.sender];
  71. require(!sender.voted);
  72.  
  73. // Self-delegation is not allowed.
  74. require(to != msg.sender);
  75.  
  76. // Forward the delegation as long as
  77. // `to` also delegated.
  78. // In general, such loops are very dangerous,
  79. // because if they run too long, they might
  80. // need more gas than is available in a block.
  81. // In this case, the delegation will not be executed,
  82. // but in other situations, such loops might
  83. // cause a contract to get "stuck" completely.
  84. while (voters[to].delegate != address(0)) {
  85. to = voters[to].delegate;
  86.  
  87. // We found a loop in the delegation, not allowed.
  88. require(to != msg.sender);
  89. }
  90.  
  91. // Since `sender` is a reference, this
  92. // modifies `voters[msg.sender].voted`
  93. sender.voted = true;
  94. sender.delegate = to;
  95. Voter storage delegate_ = voters[to];
  96. if (delegate_.voted) {
  97. // If the delegate already voted,
  98. // directly add to the number of votes
  99. proposals[delegate_.vote].voteCount += sender.weight;
  100. } else {
  101. // If the delegate did not vote yet,
  102. // add to her weight.
  103. delegate_.weight += sender.weight;
  104. }
  105. }
  106.  
  107. /// Give your vote (including votes delegated to you)
  108. /// to proposal `proposals[proposal].name`.
  109. function vote(uint proposal) public {
  110. Voter storage sender = voters[msg.sender];
  111. require(!sender.voted);
  112. sender.voted = true;
  113. sender.vote = proposal;
  114.  
  115. // If `proposal` is out of the range of the array,
  116. // this will throw automatically and revert all
  117. // changes.
  118. proposals[proposal].voteCount += sender.weight;
  119. }
  120.  
  121. /// @dev Computes the winning proposal taking all
  122. /// previous votes into account.
  123. function winningProposal() public view
  124. returns (uint winningProposal_)
  125. {
  126. uint winningVoteCount = 0;
  127. for (uint p = 0; p < proposals.length; p++) {
  128. if (proposals[p].voteCount > winningVoteCount) {
  129. winningVoteCount = proposals[p].voteCount;
  130. winningProposal_ = p;
  131. }
  132. }
  133. }
  134.  
  135. // Calls winningProposal() function to get the index
  136. // of the winner contained in the proposals array and then
  137. // returns the name of the winner
  138. function winnerName() public view
  139. returns (bytes32 winnerName_)
  140. {
  141. winnerName_ = proposals[winningProposal()].name;
  142. }
  143. }

Possible Improvements¶

Currently, many transactions are needed to assign the rightsto vote to all participants. Can you think of a better way?

Blind Auction¶

In this section, we will show how easy it is to create acompletely blind auction contract on Ethereum.We will start with an open auction where everyonecan see the bids that are made and then extend thiscontract into a blind auction where it is notpossible to see the actual bid until the biddingperiod ends.

Simple Open Auction¶

The general idea of the following simple auction contractis that everyone can send their bids duringa bidding period. The bids already include sendingmoney / ether in order to bind the bidders to theirbid. If the highest bid is raised, the previouslyhighest bidder gets her money back.After the end of the bidding period, thecontract has to be called manually for thebeneficiary to receive his money - contracts cannotactivate themselves.

  1. pragma solidity ^0.4.21;
  2.  
  3. contract SimpleAuction {
  4. // Parameters of the auction. Times are either
  5. // absolute unix timestamps (seconds since 1970-01-01)
  6. // or time periods in seconds.
  7. address public beneficiary;
  8. uint public auctionEnd;
  9.  
  10. // Current state of the auction.
  11. address public highestBidder;
  12. uint public highestBid;
  13.  
  14. // Allowed withdrawals of previous bids
  15. mapping(address => uint) pendingReturns;
  16.  
  17. // Set to true at the end, disallows any change
  18. bool ended;
  19.  
  20. // Events that will be fired on changes.
  21. event HighestBidIncreased(address bidder, uint amount);
  22. event AuctionEnded(address winner, uint amount);
  23.  
  24. // The following is a so-called natspec comment,
  25. // recognizable by the three slashes.
  26. // It will be shown when the user is asked to
  27. // confirm a transaction.
  28.  
  29. /// Create a simple auction with `_biddingTime`
  30. /// seconds bidding time on behalf of the
  31. /// beneficiary address `_beneficiary`.
  32. function SimpleAuction(
  33. uint _biddingTime,
  34. address _beneficiary
  35. ) public {
  36. beneficiary = _beneficiary;
  37. auctionEnd = now + _biddingTime;
  38. }
  39.  
  40. /// Bid on the auction with the value sent
  41. /// together with this transaction.
  42. /// The value will only be refunded if the
  43. /// auction is not won.
  44. function bid() public payable {
  45. // No arguments are necessary, all
  46. // information is already part of
  47. // the transaction. The keyword payable
  48. // is required for the function to
  49. // be able to receive Ether.
  50.  
  51. // Revert the call if the bidding
  52. // period is over.
  53. require(now <= auctionEnd);
  54.  
  55. // If the bid is not higher, send the
  56. // money back.
  57. require(msg.value > highestBid);
  58.  
  59. if (highestBid != 0) {
  60. // Sending back the money by simply using
  61. // highestBidder.send(highestBid) is a security risk
  62. // because it could execute an untrusted contract.
  63. // It is always safer to let the recipients
  64. // withdraw their money themselves.
  65. pendingReturns[highestBidder] += highestBid;
  66. }
  67. highestBidder = msg.sender;
  68. highestBid = msg.value;
  69. emit HighestBidIncreased(msg.sender, msg.value);
  70. }
  71.  
  72. /// Withdraw a bid that was overbid.
  73. function withdraw() public returns (bool) {
  74. uint amount = pendingReturns[msg.sender];
  75. if (amount > 0) {
  76. // It is important to set this to zero because the recipient
  77. // can call this function again as part of the receiving call
  78. // before `send` returns.
  79. pendingReturns[msg.sender] = 0;
  80.  
  81. if (!msg.sender.send(amount)) {
  82. // No need to call throw here, just reset the amount owing
  83. pendingReturns[msg.sender] = amount;
  84. return false;
  85. }
  86. }
  87. return true;
  88. }
  89.  
  90. /// End the auction and send the highest bid
  91. /// to the beneficiary.
  92. function auctionEnd() public {
  93. // It is a good guideline to structure functions that interact
  94. // with other contracts (i.e. they call functions or send Ether)
  95. // into three phases:
  96. // 1. checking conditions
  97. // 2. performing actions (potentially changing conditions)
  98. // 3. interacting with other contracts
  99. // If these phases are mixed up, the other contract could call
  100. // back into the current contract and modify the state or cause
  101. // effects (ether payout) to be performed multiple times.
  102. // If functions called internally include interaction with external
  103. // contracts, they also have to be considered interaction with
  104. // external contracts.
  105.  
  106. // 1. Conditions
  107. require(now >= auctionEnd); // auction did not yet end
  108. require(!ended); // this function has already been called
  109.  
  110. // 2. Effects
  111. ended = true;
  112. emit AuctionEnded(highestBidder, highestBid);
  113.  
  114. // 3. Interaction
  115. beneficiary.transfer(highestBid);
  116. }
  117. }

Blind Auction¶

The previous open auction is extended to a blind auctionin the following. The advantage of a blind auction isthat there is no time pressure towards the end ofthe bidding period. Creating a blind auction on atransparent computing platform might sound like acontradiction, but cryptography comes to the rescue.

During the bidding period, a bidder does notactually send her bid, but only a hashed version of it.Since it is currently considered practically impossibleto find two (sufficiently long) values whose hashvalues are equal, the bidder commits to the bid by that.After the end of the bidding period, the bidders haveto reveal their bids: They send their valuesunencrypted and the contract checks that the hash valueis the same as the one provided during the bidding period.

Another challenge is how to make the auctionbinding and blind at the same time: The only way toprevent the bidder from just not sending the moneyafter he won the auction is to make her send ittogether with the bid. Since value transfers cannotbe blinded in Ethereum, anyone can see the value.

The following contract solves this problem byaccepting any value that is larger than the highestbid. Since this can of course only be checked duringthe reveal phase, some bids might be invalid, andthis is on purpose (it even provides an explicitflag to place invalid bids with high value transfers):Bidders can confuse competition by placing severalhigh or low invalid bids.

  1. pragma solidity ^0.4.21;
  2.  
  3. contract BlindAuction {
  4. struct Bid {
  5. bytes32 blindedBid;
  6. uint deposit;
  7. }
  8.  
  9. address public beneficiary;
  10. uint public biddingEnd;
  11. uint public revealEnd;
  12. bool public ended;
  13.  
  14. mapping(address => Bid[]) public bids;
  15.  
  16. address public highestBidder;
  17. uint public highestBid;
  18.  
  19. // Allowed withdrawals of previous bids
  20. mapping(address => uint) pendingReturns;
  21.  
  22. event AuctionEnded(address winner, uint highestBid);
  23.  
  24. /// Modifiers are a convenient way to validate inputs to
  25. /// functions. `onlyBefore` is applied to `bid` below:
  26. /// The new function body is the modifier's body where
  27. /// `_` is replaced by the old function body.
  28. modifier onlyBefore(uint _time) { require(now < _time); _; }
  29. modifier onlyAfter(uint _time) { require(now > _time); _; }
  30.  
  31. function BlindAuction(
  32. uint _biddingTime,
  33. uint _revealTime,
  34. address _beneficiary
  35. ) public {
  36. beneficiary = _beneficiary;
  37. biddingEnd = now + _biddingTime;
  38. revealEnd = biddingEnd + _revealTime;
  39. }
  40.  
  41. /// Place a blinded bid with `_blindedBid` = keccak256(value,
  42. /// fake, secret).
  43. /// The sent ether is only refunded if the bid is correctly
  44. /// revealed in the revealing phase. The bid is valid if the
  45. /// ether sent together with the bid is at least "value" and
  46. /// "fake" is not true. Setting "fake" to true and sending
  47. /// not the exact amount are ways to hide the real bid but
  48. /// still make the required deposit. The same address can
  49. /// place multiple bids.
  50. function bid(bytes32 _blindedBid)
  51. public
  52. payable
  53. onlyBefore(biddingEnd)
  54. {
  55. bids[msg.sender].push(Bid({
  56. blindedBid: _blindedBid,
  57. deposit: msg.value
  58. }));
  59. }
  60.  
  61. /// Reveal your blinded bids. You will get a refund for all
  62. /// correctly blinded invalid bids and for all bids except for
  63. /// the totally highest.
  64. function reveal(
  65. uint[] _values,
  66. bool[] _fake,
  67. bytes32[] _secret
  68. )
  69. public
  70. onlyAfter(biddingEnd)
  71. onlyBefore(revealEnd)
  72. {
  73. uint length = bids[msg.sender].length;
  74. require(_values.length == length);
  75. require(_fake.length == length);
  76. require(_secret.length == length);
  77.  
  78. uint refund;
  79. for (uint i = 0; i < length; i++) {
  80. var bid = bids[msg.sender][i];
  81. var (value, fake, secret) =
  82. (_values[i], _fake[i], _secret[i]);
  83. if (bid.blindedBid != keccak256(value, fake, secret)) {
  84. // Bid was not actually revealed.
  85. // Do not refund deposit.
  86. continue;
  87. }
  88. refund += bid.deposit;
  89. if (!fake && bid.deposit >= value) {
  90. if (placeBid(msg.sender, value))
  91. refund -= value;
  92. }
  93. // Make it impossible for the sender to re-claim
  94. // the same deposit.
  95. bid.blindedBid = bytes32(0);
  96. }
  97. msg.sender.transfer(refund);
  98. }
  99.  
  100. // This is an "internal" function which means that it
  101. // can only be called from the contract itself (or from
  102. // derived contracts).
  103. function placeBid(address bidder, uint value) internal
  104. returns (bool success)
  105. {
  106. if (value <= highestBid) {
  107. return false;
  108. }
  109. if (highestBidder != 0) {
  110. // Refund the previously highest bidder.
  111. pendingReturns[highestBidder] += highestBid;
  112. }
  113. highestBid = value;
  114. highestBidder = bidder;
  115. return true;
  116. }
  117.  
  118. /// Withdraw a bid that was overbid.
  119. function withdraw() public {
  120. uint amount = pendingReturns[msg.sender];
  121. if (amount > 0) {
  122. // It is important to set this to zero because the recipient
  123. // can call this function again as part of the receiving call
  124. // before `transfer` returns (see the remark above about
  125. // conditions -> effects -> interaction).
  126. pendingReturns[msg.sender] = 0;
  127.  
  128. msg.sender.transfer(amount);
  129. }
  130. }
  131.  
  132. /// End the auction and send the highest bid
  133. /// to the beneficiary.
  134. function auctionEnd()
  135. public
  136. onlyAfter(revealEnd)
  137. {
  138. require(!ended);
  139. emit AuctionEnded(highestBidder, highestBid);
  140. ended = true;
  141. beneficiary.transfer(highestBid);
  142. }
  143. }

Safe Remote Purchase¶

  1. pragma solidity ^0.4.21;
  2.  
  3. contract Purchase {
  4. uint public value;
  5. address public seller;
  6. address public buyer;
  7. enum State { Created, Locked, Inactive }
  8. State public state;
  9.  
  10. // Ensure that `msg.value` is an even number.
  11. // Division will truncate if it is an odd number.
  12. // Check via multiplication that it wasn't an odd number.
  13. function Purchase() public payable {
  14. seller = msg.sender;
  15. value = msg.value / 2;
  16. require((2 * value) == msg.value);
  17. }
  18.  
  19. modifier condition(bool _condition) {
  20. require(_condition);
  21. _;
  22. }
  23.  
  24. modifier onlyBuyer() {
  25. require(msg.sender == buyer);
  26. _;
  27. }
  28.  
  29. modifier onlySeller() {
  30. require(msg.sender == seller);
  31. _;
  32. }
  33.  
  34. modifier inState(State _state) {
  35. require(state == _state);
  36. _;
  37. }
  38.  
  39. event Aborted();
  40. event PurchaseConfirmed();
  41. event ItemReceived();
  42.  
  43. /// Abort the purchase and reclaim the ether.
  44. /// Can only be called by the seller before
  45. /// the contract is locked.
  46. function abort()
  47. public
  48. onlySeller
  49. inState(State.Created)
  50. {
  51. emit Aborted();
  52. state = State.Inactive;
  53. seller.transfer(this.balance);
  54. }
  55.  
  56. /// Confirm the purchase as buyer.
  57. /// Transaction has to include `2 * value` ether.
  58. /// The ether will be locked until confirmReceived
  59. /// is called.
  60. function confirmPurchase()
  61. public
  62. inState(State.Created)
  63. condition(msg.value == (2 * value))
  64. payable
  65. {
  66. emit PurchaseConfirmed();
  67. buyer = msg.sender;
  68. state = State.Locked;
  69. }
  70.  
  71. /// Confirm that you (the buyer) received the item.
  72. /// This will release the locked ether.
  73. function confirmReceived()
  74. public
  75. onlyBuyer
  76. inState(State.Locked)
  77. {
  78. emit ItemReceived();
  79. // It is important to change the state first because
  80. // otherwise, the contracts called using `send` below
  81. // can call in again here.
  82. state = State.Inactive;
  83.  
  84. // NOTE: This actually allows both the buyer and the seller to
  85. // block the refund - the withdraw pattern should be used.
  86.  
  87. buyer.transfer(value);
  88. seller.transfer(this.balance);
  89. }
  90. }

Micropayment Channel¶

To be written.

原文: http://solidity.apachecn.org/cn/doc/v0.4.21/solidity-by-example.html