diff options
author | chriseth <c@ethdev.com> | 2015-12-08 04:16:25 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2015-12-08 05:35:51 +0800 |
commit | 64fa9ca478b95970825e3b783275ee66cf848358 (patch) | |
tree | 0e968e61d604d86197151a69ad38d0d2b07daa80 /docs/solidity-by-example.rst | |
parent | ba8bc4565aa0e5c41be107a7bbae3b51c74679c6 (diff) | |
download | dexon-solidity-64fa9ca478b95970825e3b783275ee66cf848358.tar.gz dexon-solidity-64fa9ca478b95970825e3b783275ee66cf848358.tar.zst dexon-solidity-64fa9ca478b95970825e3b783275ee66cf848358.zip |
Imported documentation.
Diffstat (limited to 'docs/solidity-by-example.rst')
-rw-r--r-- | docs/solidity-by-example.rst | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst new file mode 100644 index 00000000..de6e8165 --- /dev/null +++ b/docs/solidity-by-example.rst @@ -0,0 +1,530 @@ +################### +Solidity by Example +################### + +.. index:: voting, ballot + +.. _voting: + +****** +Voting +****** + +The following contract is quite complex, but showcases +a lot of Solidity's features. It implements a voting +contract. Of course, the main problems of electronic +voting is how to assign voting rights to the correct +persons and how to prevent manipulation. We will not +solve all problems here, but at least we will show +how delegated voting can be done so that vote counting +is **automatic and completely transparent** at the +same 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 as +chairperson will give the right to vote to each +address individually. + +The persons behind the addresses can then choose +to either vote themselves or to delegate their +vote to a person they trust. + +At the end of the voting time, `winningProposal()` +will return the proposal with the largest number +of votes. + +.. Gist: 618560d3f740204d46a5 + +:: + + /// @title Voting with delegation. + contract Ballot + { + // This declares a new complex type which will + // be used for variables later. + // It will represent a single voter. + struct Voter + { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + // This is a type for a single proposal. + struct Proposal + { + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + // This declares a state variable that + // stores a `Voter` struct for each possible address. + mapping(address => Voter) public voters; + // A dynamically-sized array of `Proposal` structs. + Proposal[] public proposals; + + /// Create a new ballot to choose one of `proposalNames`. + function Ballot(bytes32[] proposalNames) + { + chairperson = msg.sender; + voters[chairperson].weight = 1; + // For each of the provided proposal names, + // create a new proposal object and add it + // to the end of the array. + for (uint i = 0; i < proposalNames.length; i++) + // `Proposal({...})` creates a temporary + // Proposal object and `proposal.push(...)` + // appends it to the end of `proposals`. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + + // Give `voter` the right to vote on this ballot. + // May only be called by `chairperson`. + function giveRightToVote(address voter) + { + if (msg.sender != chairperson || voters[voter].voted) + // `throw` terminates and reverts all changes to + // the state and to Ether balances. It is often + // a good idea to use this if functions are + // called incorrectly. But watch out, this + // will also consume all provided gas. + throw; + voters[voter].weight = 1; + } + + /// Delegate your vote to the voter `to`. + function delegate(address to) + { + // assigns reference + Voter sender = voters[msg.sender]; + if (sender.voted) + throw; + // Forward the delegation as long as + // `to` also delegated. + while (voters[to].delegate != address(0) && + voters[to].delegate != msg.sender) + to = voters[to].delegate; + // We found a loop in the delegation, not allowed. + if (to == msg.sender) + throw; + // Since `sender` is a reference, this + // modifies `voters[msg.sender].voted` + sender.voted = true; + sender.delegate = to; + Voter delegate = voters[to]; + if (delegate.voted) + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate.vote].voteCount += sender.weight; + else + // If the delegate did not vote yet, + // add to her weight. + delegate.weight += sender.weight; + } + + /// Give your vote (including votes delegated to you) + /// to proposal `proposals[proposal].name`. + function vote(uint proposal) + { + Voter sender = voters[msg.sender]; + if (sender.voted) throw; + sender.voted = true; + sender.vote = proposal; + // If `proposal` is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /// @dev Computes the winning proposal taking all + /// previous votes into account. + function winningProposal() constant + returns (uint winningProposal) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) + { + if (proposals[p].voteCount > winningVoteCount) + { + winningVoteCount = proposals[p].voteCount; + winningProposal = p; + } + } + } + } + +Possible Improvements +===================== + +Currently, many transactions are needed to assign the rights +to vote to all participants. Can you think of a better way? + +.. index:: auction;blind, auction;open, blind auction, open auction + +************* +Blind Auction +************* + +In this section, we will show how easy it is to create a +completely blind auction contract on Ethereum. +We will start with an open auction where everyone +can see the bids that are made and then extend this +contract into a blind auction where it is not +possible to see the actual bid until the bidding +period ends. + +Simple Open Auction +=================== + +The general idea of the following simple auction contract +is that everyone can send their bids during +a bidding period. The bids already include sending +money / ether in order to bind the bidders to their +bid. If the highest bid is raised, the previously +highest bidder gets her money back. +After the end of the bidding period, the +contract has to be called manually for the +beneficiary to receive his money - contracts cannot +activate themselves. + +.. {% include open_link gist="48cd2b65ff83bd04f7af" %} + +:: + + contract SimpleAuction { + // Parameters of the auction. Times are either + // absolute unix timestamps (seconds since 1970-01-01) + // ore time periods in seconds. + address public beneficiary; + uint public auctionStart; + uint public biddingTime; + + // Current state of the auction. + address public highestBidder; + uint public highestBid; + + // Set to true at the end, disallows any change + bool ended; + + // Events that will be fired on changes. + event HighestBidIncreased(address bidder, uint amount); + event AuctionEnded(address winner, uint amount); + + // The following is a so-called natspec comment, + // recognizable by the three slashes. + // It will be shown when the user is asked to + // confirm a transaction. + + /// Create a simple auction with `_biddingTime` + /// seconds bidding time on behalf of the + /// beneficiary address `_beneficiary`. + function SimpleAuction(uint _biddingTime, + address _beneficiary) { + beneficiary = _beneficiary; + auctionStart = now; + biddingTime = _biddingTime; + } + + /// Bid on the auction with the value sent + /// together with this transaction. + /// The value will only be refunded if the + /// auction is not won. + function bid() { + // No arguments are necessary, all + // information is already part of + // the transaction. + if (now > auctionStart + biddingTime) + // Revert the call if the bidding + // period is over. + throw; + if (msg.value <= highestBid) + // If the bid is not higher, send the + // money back. + throw; + if (highestBidder != 0) + highestBidder.send(highestBid); + highestBidder = msg.sender; + highestBid = msg.value; + HighestBidIncreased(msg.sender, msg.value); + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() { + if (now <= auctionStart + biddingTime) + throw; // auction did not yet end + if (ended) + throw; // this function has already been called + AuctionEnded(highestBidder, highestBid); + // We send all the money we have, because some + // of the refunds might have failed. + beneficiary.send(this.balance); + ended = true; + } + + function () { + // This function gets executed if a + // transaction with invalid data is sent to + // the contract or just ether without data. + // We revert the send so that no-one + // accidentally loses money when using the + // contract. + throw; + } + } + +Blind Auction +================ + +The previous open auction is extended to a blind auction +in the following. The advantage of a blind auction is +that there is no time pressure towards the end of +the bidding period. Creating a blind auction on a +transparent computing platform might sound like a +contradiction, but cryptography comes to the rescue. + +During the **bidding period**, a bidder does not +actually send her bid, but only a hashed version of it. +Since it is currently considered practically impossible +to find two (sufficiently long) values whose hash +values are equal, the bidder commits to the bid by that. +After the end of the bidding period, the bidders have +to reveal their bids: They send their values +unencrypted and the contract checks that the hash value +is the same as the one provided during the bidding period. + +Another challenge is how to make the auction +**binding and blind** at the same time: The only way to +prevent the bidder from just not sending the money +after he won the auction is to make her send it +together with the bid. Since value transfers cannot +be blinded in Ethereum, anyone can see the value. + +The following contract solves this problem by +accepting any value that is at least as large as +the bid. Since this can of course only be checked during +the reveal phase, some bids might be **invalid**, and +this is on purpose (it even provides an explicit +flag to place invalid bids with high value transfers): +Bidders can confuse competition by placing several +high or low invalid bids. + + +.. {% include open_link gist="70528429c2cd867dd1d6" %} + +:: + + contract BlindAuction + { + struct Bid + { + bytes32 blindedBid; + uint deposit; + } + address public beneficiary; + uint public auctionStart; + uint public biddingEnd; + uint public revealEnd; + bool public ended; + + mapping(address => Bid[]) public bids; + + address public highestBidder; + uint public highestBid; + + event AuctionEnded(address winner, uint highestBid); + + /// Modifiers are a convenient way to validate inputs to + /// functions. `onlyBefore` is applied to `bid` below: + /// The new function body is the modifier's body where + /// `_` is replaced by the old function body. + modifier onlyBefore(uint _time) { if (now >= _time) throw; _ } + modifier onlyAfter(uint _time) { if (now <= _time) throw; _ } + + function BlindAuction(uint _biddingTime, + uint _revealTime, + address _beneficiary) + { + beneficiary = _beneficiary; + auctionStart = now; + biddingEnd = now + _biddingTime; + revealEnd = biddingEnd + _revealTime; + } + + /// Place a blinded bid with `_blindedBid` = sha3(value, + /// fake, secret). + /// The sent ether is only refunded if the bid is correctly + /// revealed in the revealing phase. The bid is valid if the + /// ether sent together with the bid is at least "value" and + /// "fake" is not true. Setting "fake" to true and sending + /// not the exact amount are ways to hide the real bid but + /// still make the required deposit. The same address can + /// place multiple bids. + function bid(bytes32 _blindedBid) + onlyBefore(biddingEnd) + { + bids[msg.sender].push(Bid({ + blindedBid: _blindedBid, + deposit: msg.value + })); + } + + /// Reveal your blinded bids. You will get a refund for all + /// correctly blinded invalid bids and for all bids except for + /// the totally highest. + function reveal(uint[] _values, bool[] _fake, + bytes32[] _secret) + onlyAfter(biddingEnd) + onlyBefore(revealEnd) + { + uint length = bids[msg.sender].length; + if (_values.length != length || _fake.length != length || + _secret.length != length) + throw; + uint refund; + for (uint i = 0; i < length; i++) + { + var bid = bids[msg.sender][i]; + var (value, fake, secret) = + (_values[i], _fake[i], _secret[i]); + if (bid.blindedBid != sha3(value, fake, secret)) + // Bid was not actually revealed. + // Do not refund deposit. + continue; + refund += value; + if (!fake && bid.deposit >= value) + if (placeBid(msg.sender, value)) + refund -= value; + // Make it impossible for the sender to re-claim + // the same deposit. + bid.blindedBid = 0; + } + msg.sender.send(refund); + } + + // This is an "internal" function which means that it + // can only be called from the contract itself (or from + // derived contracts). + function placeBid(address bidder, uint value) internal + returns (bool success) + { + if (value <= highestBid) + return false; + if (highestBidder != 0) + // Refund the previously highest bidder. + highestBidder.send(highestBid); + highestBid = value; + highestBidder = bidder; + return true; + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() + onlyAfter(revealEnd) + { + if (ended) throw; + AuctionEnded(highestBidder, highestBid); + // We send all the money we have, because some + // of the refunds might have failed. + beneficiary.send(this.balance); + ended = true; + } + + function () { throw; } + } + +.. index:: purchase, remote purchase, escrow + +******************** +Safe Remote Purchase +******************** + +.. {% include open_link gist="b16e8e76a423b7671e99" %} + +:: + + contract Purchase + { + uint public value; + address public seller; + address public buyer; + enum State { Created, Locked, Inactive } + State public state; + function Purchase() + { + seller = msg.sender; + value = msg.value / 2; + if (2 * value != msg.value) throw; + } + modifier require(bool _condition) + { + if (!_condition) throw; + _ + } + modifier onlyBuyer() + { + if (msg.sender != buyer) throw; + _ + } + modifier onlySeller() + { + if (msg.sender != seller) throw; + _ + } + modifier inState(State _state) + { + if (state != _state) throw; + _ + } + event aborted(); + event purchaseConfirmed(); + event itemReceived(); + + /// Abort the purchase and reclaim the ether. + /// Can only be called by the seller before + /// the contract is locked. + function abort() + onlySeller + inState(State.Created) + { + aborted(); + seller.send(this.balance); + state = State.Inactive; + } + /// Confirm the purchase as buyer. + /// Transaction has to include `2 * value` ether. + /// The ether will be locked until confirmReceived + /// is called. + function confirmPurchase() + inState(State.Created) + require(msg.value == 2 * value) + { + purchaseConfirmed(); + buyer = msg.sender; + state = State.Locked; + } + /// Confirm that you (the buyer) received the item. + /// This will release the locked ether. + function confirmReceived() + onlyBuyer + inState(State.Locked) + { + itemReceived(); + buyer.send(value); // We ignore the return value on purpose + seller.send(this.balance); + state = State.Inactive; + } + function() { throw; } + } + +******************** +Micropayment Channel +******************** + +To be written. |