寫在前面:HiBlock區塊鏈社區成立了翻譯小組(以太坊中文社區),翻譯區塊鏈相關的技術文檔及資料,本文爲Solidity官方文檔翻譯的第三部分《根據例子學習Solidity》,特發佈出來邀請solidity愛好者、開發者作公開的審校,您能夠添加微信baobaotalk_com,驗證輸入「solidity」,而後將您的意見和建議發送給咱們,也能夠在文末「留言」區留言,有效的建議咱們會採納及合併進下一版本,同時將送一份小禮物給您以示感謝。數組
如下的合約至關複雜,但展現了不少Solidity的功能。它實現了一個投票合約。 固然,電子投票的主要問題是如何將投票權分配給正確的人員以及如何防止被操縱。 咱們不會在這裏解決全部的問題,但至少咱們會展現如何進行委託投票,同時,計票又是自動和徹底透明的 。安全
咱們的想法是爲每一個(投票)表決建立一份合約,爲每一個選項提供簡稱。 而後做爲合約的創造者——即主席,將給予每一個獨立的地址以投票權。微信
地址後面的人能夠選擇本身投票,或者委託給他們信任的人來投票。app
在投票時間結束時,winningProposal() 將返回得到最多投票的提案。函數
pragma solidity ^0.4.16;學習
/// @title 委託投票區塊鏈
contract Ballot { * // 這裏聲明瞭一個新的複合類型用於稍後的變量* * // 它用來表示一個選民* ** struct** Voter { uint weight;* // 計票的權重* ** bool** voted; // 若爲真,表明該人已投票 address delegate; // 被委託人 ** uint** vote; // 投票提案的索引 }優化
* // 提案的類型* struct Proposal { bytes32 name; // 簡稱(最長32個字節) ** uint** voteCount; // 得票數 }ui
** address** public chairperson;this
* // 這聲明瞭一個狀態變量,爲每一個可能的地址存儲一個 Voter
。* mapping(address => Voter) public voters;
* // 一個 Proposal
結構類型的動態數組* Proposal[] public proposals;
/// 爲 proposalNames
中的每一個提案,建立一個新的(投票)表決 function Ballot(bytes32[] proposalNames) public { chairperson = msg.sender; voters[chairperson].weight = 1; * //對於提供的每一個提案名稱,* * //建立一個新的 Proposal 對象並把它添加到數組的末尾。* ** for** (uint i = 0; i < proposalNames.length; i**++**) { * // Proposal({...})
建立一個臨時 Proposal 對象,* * // proposals.push(...)
將其添加到 proposals
的末尾* proposals.push(Proposal({ name: proposalNames[i], voteCount: 0 })); } }
// 受權 voter
對這個(投票)表決進行投票 // 只有 chairperson
能夠調用該函數。 ** function** giveRightToVote(address voter) public { * // 若 require
的入參斷定爲 false
, // 則終止函數,恢復全部對狀態和以太幣帳戶的變更,而且也不會消耗 gas 。 // 若是函數被錯誤的調用,使用require,將是一個很好的選擇。* require( (msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0) ); voters[voter].weight = 1; }
/// 把你的投票委託到投票者 to
。 function delegate(address to) *public { * // 傳引用 Voter storage sender = voters[msg.sender]; require(!**sender.voted);
* // 委託給本身是不容許的* require(to** !=** msg.sender);
* // 委託是能夠傳遞的,只要被委託者 to
也設置了委託。 // 通常來講,這種循環委託是危險的。由於,若是傳遞的鏈條太長, // 則可能需消耗的gas要多於區塊中剩餘的(大於區塊設置的gasLimit), // 這種狀況下,委託不會被執行。 // 而在另外一些狀況下,若是造成閉環,則會讓合約徹底卡住。* while (voters[to].delegate != address(0)) { to = voters[to].delegate;
* // 不容許閉環委託* require(to != msg.sender); }
* // sender
是一個引用, 至關於對 voters[msg.sender].voted
進行修改* sender.voted = true; sender.delegate = to; Voter storage delegate_ = voters[to]; **if (delegate_.voted) { * // 若被委託者已經投過票了,直接增長得票數 proposals[delegate_.vote].voteCount += sender.weight; } else { * // 若被委託者還沒投票,增長委託者的權重 delegate_.weight += sender.weight; } }
* /// 把你的票(包括委託給你的票), /// 投給提案 proposals[proposal].name
.* function vote(uint proposal) public { Voter storage sender = voters[msg.sender]; require(**!**sender.voted); sender.voted = true; sender.vote = proposal;
* // 若是 proposal
超過了數組的範圍,則會自動拋出異常,並恢復全部的改動* proposals[proposal].voteCount** +=** sender.weight; }
/// @dev 結合以前全部的投票,計算出最終勝出的提案 ** function** winningProposal() public view ** returns** (uint winningProposal_) { ** uint** winningVoteCount = 0; ** for** (uint p = 0; p < proposals.length; p**++) { ** if (proposals[p].voteCount > winningVoteCount) { winningVoteCount = proposals[p].voteCount; winningProposal_ = p; } } }
// 調用 winningProposal() 函數以獲取提案數組中獲勝者的索引,並以此返回獲勝者的名稱 ** function** winnerName() public view ** returns** (bytes32 winnerName_) { winnerName_ **= **proposals[winningProposal()].name; }
}
可能的優化
當前,爲了把投票權分配給全部參與者,須要執行不少交易。你有沒有更好的主意?
在本節中,咱們將展現如何輕鬆地在以太坊上建立一個祕密競價的合約。 咱們將從公開拍賣開始,每一個人均可以看到出價,而後將此合約擴展到盲拍合約, 在競標期結束以前沒法看到實際出價。
簡單的公開拍賣
如下簡單的拍賣合約的整體思路是每一個人均可以在投標期內發送他們的出價。 出價已經包含了資金/以太幣,來將投標人與他們的投標綁定。 若是最高出價提升了(被其餘出價者的出價超過),以前出價最高的出價者能夠拿回她的錢。 在投標期結束後,受益人須要手動調用合約來接收他的錢 - 合約不能本身激活接收。
pragma solidity ^0.4.21;
contract SimpleAuction { * // 拍賣的參數。* address public beneficiary; * // 時間是unix的絕對時間戳(自1970-01-01以來的秒數) // 或以秒爲單位的時間段。* uint public auctionEnd;
* // 拍賣的當前狀態* ** address** public highestBidder; uint public highestBid;
** //能夠取回的以前的出價** mapping(address => uint) pendingReturns;
* // 拍賣結束後設爲 true,將禁止全部的變動* bool ended;
* // 變動觸發的事件* ** event** HighestBidIncreased(address bidder, uint amount); event AuctionEnded(address winner, uint amount);
// 如下是所謂的 natspec 註釋,能夠經過三個斜槓來識別。 // 當用戶被要求確認交易時將顯示。 * /// 以受益者地址 _beneficiary
的名義, /// 建立一個簡單的拍賣,拍賣時間爲 _biddingTime
秒。* ** function** SimpleAuction( ** uint** _biddingTime, address _beneficiary ) public { beneficiary = _beneficiary; auctionEnd **= **now + _biddingTime; }
/// 對拍賣進行出價,具體的出價隨交易一塊兒發送。 /// 若是沒有在拍賣中勝出,則返還出價。 ** function** bid() public payable { * // 參數不是必要的。由於全部的信息已經包含在了交易中。 // 對於能接收以太幣的函數,關鍵字 payable 是必須的。
// 若是拍賣已結束,撤銷函數的調用。* require(now <= auctionEnd);
* // 若是出價不夠高,返還你的錢* require(msg.value > highestBid);
** if** (highestBid != 0) { * // 返還出價時,簡單地直接調用 highestBidder.send(highestBid) 函數, // 是有安全風險的,由於它有可能執行一個非信任合約。 // 更爲安全的作法是讓接收方本身提取金錢。* pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; highestBid = msg.value; emit HighestBidIncreased(msg.sender, msg.value); }
* /// 取回出價(當該出價已被超越)* ** function** withdraw() public returns (bool) { uint amount = pendingReturns[msg.sender]; ** if **(amount **> *0) { * // 這裏很重要,首先要設零值。 // 由於,做爲接收調用的一部分, // 接收者能夠在 send
返回以前,從新調用該函數。 pendingReturns[msg.sender] = 0;
** if** (*!msg.sender.send(amount)) { * // 這裏不需拋出異常,只需重置未付款 pendingReturns[msg.sender] = amount; return false; } } ** return true; }
* /// 結束拍賣,並把最高的出價發送給受益人* ** function** auctionEnd() public { * // 對於可與其餘合約交互的函數(意味着它會調用其餘函數或發送以太幣), // 一個好的指導方針是將其結構分爲三個階段: // 1. 檢查條件 // 2. 執行動做 (可能會改變條件) // 3. 與其餘合約交互 // 若是這些階段相混合,其餘的合約可能會回調當前合約並修改狀態, // 或者致使某些效果(好比支付以太幣)屢次生效。 // 若是合約內調用的函數包含了與外部合約的交互, // 則它也會被認爲是與外部合約有交互的。
// 1. 條件* require(now >= auctionEnd); // 拍賣還沒有結束 require(**!*ended); // 該函數已被調用 * // 2. 生效 ended = true; emit AuctionEnded(highestBidder, highestBid);
* // 3. 交互* beneficiary.transfer(highestBid); }
}
祕密競拍(盲拍)
以前的公開拍賣接下來將被擴展爲一個祕密競拍。 祕密競拍的好處是在投標結束前不會有時間壓力。 在一個透明的計算平臺上進行祕密競拍聽起來像是自相矛盾,但密碼學能夠實現它。
在 投標期間 ,投標人實際上並無發送她的出價,而只是發送一個哈希版本的出價。 因爲目前幾乎不可能找到兩個(足夠長的)值,其哈希值是相等的,所以投標人可經過該方式提交報價。 在投標結束後,投標人必須公開他們的出價:他們不加密的發送他們的出價,合約檢查出價的哈希值是否與投標期間提供的相同。
另外一個挑戰是如何使拍賣同時作到 綁定和祕密 : 惟一能阻止投標者在她贏得拍賣後不付款的方式是,讓她將錢連同出價一塊兒發出。 但因爲資金轉移在 以太坊Ethereum 中不能被隱藏,所以任何人均可以看到轉移的資金。
下面的合約經過接受任何大於最高出價的值來解決這個問題。 固然,由於這隻能在披露階段進行檢查,有些出價多是 **無效 **的, 而且,這是故意的(與高出價一塊兒,它甚至提供了一個明確的標誌來標識無效的出價): 投標人能夠經過設置幾個或高或低的無效出價來迷惑競爭對手。
pragma solidity ^0.4.21;
contract BlindAuction { struct Bid { bytes32 blindedBid; uint deposit; }
address public beneficiary; uint** public** biddingEnd; ** uint**** public** revealEnd; bool** public** ended;
** mapping**(address => Bid[]) **public **bids;
address public highestBidder; ** uint** **public **highestBid;
// 能夠取回的以前的出價 mapping(address =>** uint**) pendingReturns;
event AuctionEnded(address winner, uint highestBid);
* /// 使用 modifier 能夠更便捷的校驗函數的入參。 /// onlyBefore
會被用於後面的 bid
函數: /// 新的函數體是由 modifier 自己的函數體,並用原函數體替換 _;
語句來組成的。* modifier onlyBefore(**uint **_time) { require(now **< **_time); _; } modifier onlyAfter(uint _time) { require(now > _time); _; }
** function** BlindAuction( uint _biddingTime, ** uint **_revealTime, address _beneficiary ) public { beneficiary = _beneficiary; biddingEnd = now + _biddingTime; revealEnd = biddingEnd + _revealTime; }
* /// 能夠經過 _blindedBid
= keccak256(value, fake, secret) /// 設置一個祕密競拍。 /// 只有在出價披露階段被正確披露,已發送的以太幣纔會被退還。 /// 若是與出價一塊兒發送的以太幣至少爲 「value」 且 「fake」 不爲真,則出價有效。 /// 將 「fake」 設置爲 true ,而後發送知足訂金金額但又不與出價相同的金額是隱藏實際出價的方法。 /// 同一個地址能夠放置多個出價。* function bid(bytes32 _blindedBid) ** public payable** onlyBefore(biddingEnd) { bids[msg.sender].push(Bid({ blindedBid: _blindedBid, deposit: msg.value })); }
* /// 披露你的祕密競拍出價。 /// 對於全部正確披露的無效出價以及除最高出價之外的全部出價,你都將得到退款。* ** function** reveal( uint[] _values, ** bool**[] _fake, ** bytes32**[] _secret ) public onlyAfter(biddingEnd) onlyBefore(revealEnd) { ** uint** length = bids[msg.sender].length; require(_values.length == length); require(_fake.length == length); require(_secret.length == length);
** uint** refund; ** for (uint i = 0; i < length; i++) { ** var bid = bids[msg.sender][i]; var (value, fake, secret)** =** (_values[i], _fake[i], _secret[i]); if (bid.blindedBid != keccak256(value, fake, secret)) { * // 出價未能正確披露 // 不返還訂金* continue; } refund += bid.deposit; ** if** (**!**fake && bid.deposit >= value) { *if (placeBid(msg.sender, value)) refund -= value; } * // 使發送者不可能再次認領同一筆訂金 bid.blindedBid =**bytes32(0); } msg.sender.transfer(refund); }
* // 這是一個 "internal" 函數, 意味着它只能在本合約(或繼承合約)內被調用* ** function** placeBid(address bidder, uint value) internal ** returns (bool success) { ** if (value <= highestBid) { ** return false;** } ** if** (highestBidder != 0) { * // 返還以前的最高出價* pendingReturns[highestBidder] += highestBid; } highestBid = value; highestBidder = bidder; ** return true;** }
* /// 取回出價(當該出價已被超越)* function withdraw() public { ** uint** amount = pendingReturns[msg.sender]; ** if** (amount > 0) { * // 這裏很重要,首先要設零值。 // 由於,做爲接收調用的一部分, // 接收者能夠在 transfer
返回以前從新調用該函數。(可查看上面關於‘條件 -> 影響 -> 交互’的標註)* pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount); } }
* /// 結束拍賣,並把最高的出價發送給受益人* ** function** auctionEnd() public onlyAfter(revealEnd) { require(**!**ended); emit AuctionEnded(highestBidder, highestBid); ended = true; beneficiary.transfer(highestBid); }
安全的遠程購買
pragma solidity ^0.4.21;
contract Purchase { ** uint public value; address public** seller; ** address **public buyer; enum State { Created, Locked, Inactive } State public state;
* //確保 msg.value
是一個偶數。 //若是它是一個奇數,則它將被截斷。 //經過乘法檢查它不是奇數。* ** function** Purchase() public payable { seller = msg.sender; value = msg.value / 2; require((2** *** value) == msg.value); }
modifier condition(bool _condition) { require(_condition); _; }
modifier onlyBuyer() { require(msg.sender** ==** buyer); _; }
modifier onlySeller() { require(msg.sender **== **seller); _; }
modifier inState(State _state) { require(state == _state); _; }
event Aborted(); ** event **PurchaseConfirmed(); ** event **ItemReceived();
* ///停止購買並回收以太幣。 ///只能在合約被鎖定以前由賣家調用。* function abort() ** public** onlySeller inState(State.Created) { emit Aborted(); state = State.Inactive; seller.transfer(this.balance); }
* /// 買家確認購買。 /// 交易必須包含 2 * value
個以太幣。 /// 以太幣會被鎖定,直到 confirmReceived 被調用。* ** function** confirmPurchase() public inState(State.Created) condition(msg.value == (2 * value)) ** payable** { emit PurchaseConfirmed(); buyer = msg.sender; state = State.Locked; }
/// 確認你(買家)已經收到商品。 /// 這會釋放被鎖定的以太幣。 ** function** confirmReceived() ** public** onlyBuyer inState(State.Locked) { emit ItemReceived(); * // 首先修改狀態很重要,不然的話,由 transfer
所調用的合約能夠回調進這裏(再次接收以太幣)。* state = State.Inactive;
* // 注意: 這實際上容許買方和賣方阻止退款 - 應該使用取回模式。* buyer.transfer(value); seller.transfer(this.balance); }
}
微支付通道
To be written.
注:本文爲solidity翻譯的第三部分《根據例子學習Solidity》,特發佈出來邀請solidity愛好者、開發者作公開的審校,您能夠添加微信baobaotalk_com,驗證輸入「solidity」,而後將您的意見和建議發送給咱們,也可在文末「留言」區留言,或經過原文連接訪問咱們的Github。有效的建議咱們會收納並及時改進,同時將送一份小禮物給您以示感謝。
本文內容來源於HiBlock區塊鏈社區翻譯小組-以太坊中文社區,感謝全體譯者的辛苦工做。
如下是咱們的社區介紹,歡迎各類合做、交流、學習:)