聲明:本系列文章是自己在http://solidity-cn.readthedoc... 學習solidity時,因爲英語水平不夠,被迫用谷歌粗略翻譯的。僅爲了方便學習,中間肯定有很多錯誤的地方。請勘誤。
本文原地址:http://solidity-cn.readthedoc... 以下是翻譯正文:
投票: 以下合同非常複雜,但展示了很多Solidity的功能。 它執行投票合同。 當然,電子投票的主要問題是如何爲正確的人員分配投票權,以及如何防止操縱。 我們不會在這裏解決所有問題,但至少我們會展示如何進行委派投票,以便計票自動且完全透明。
這個想法是爲每個選票創建一個合同,爲每個選項提供一個簡稱。 然後,擔任主席的合同創建者將分別給予每個地址的投票權。
然後,地址背後的人可以選擇自己投票,或者將他們的投票委託給他們信任的人。
在投票時間結束時,winningProposal()
將返回投票數最多的提案。
pragma solidity ^0.4.21 /// @title Voting with delegation. contract Ballot { //這將聲明一個新的複合類型 //稍後用於變量。 //它會代表一個voter。 struct Voter { uint weight; //weight is accumulated by delegation bool voted; // 如果true,已經voted address delegate; // 被授權的人 uint vote; // vote提案的索引 } // a type for a single proposal. struct Proposal { bytes32 name; uint voteCount; } address public chairperson; //聲明一個狀態變量,爲每個可能的地址存儲一個`Voter`結構體。 mapping(address => Voter) public voters; // Proposal結構的動態大小的數組。 Proposal[] public proposals; ///創建一個新ballot來選擇`proposalNames`中的一個。 function Ballot(bytes32[] proposalNames) public { chairperson = msg.sender; voters[chairperson].weight = 1; //對於提供的每個提議名稱,創建一個新的提議對象並將其添加到數組的末尾。 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((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0)) voters[voter].weight = 1; } function delegate(address to) public { Voter storage sender = voters[msg.sender]; require(!sender.voted); require(to != msg.sender); // 一般來說,這樣的循環是非常危險的,因爲如果它們運行時間太長, // 它們可能需要比塊中可用的更多的氣體。在這種情況下,委託將不會被執行, // 但在其他情況下,這樣的循環可能導致合同 完全“卡住”。 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; } } ///將您的投票(包括授予您的投票)提交給提案[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 { //拍賣的參數。 時間是絕對的unix時間戳 //(自1970-01-01以來的秒數)或以秒爲單位的時間段。 address public beneficiary; 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 { //不需要參數,所有信息已經是交易的一部分。 爲了能夠接收以太網,功能需要關鍵字。 //如果結束,請恢復通話。 require(now <= auctionEnd); // 如果出價不高,將錢退回。 require(msg.value > highestBid); if (highestBid != 0) { //通過使用highestBidder.send(highestBid)發送回款有安全風險, // 因爲它可以執行不可信的合同。 // 讓收款人自己收回錢會比較安全。 pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; highestBid = msg.value; 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 { //將與其他合約交互的函數(即它們調用函數或發送Ether)結構化爲三個階段是一個很好的指導: // 1.檢查條件 // 2.執行操作(潛在的變化條件) // 3.與其他合同交互 //如果這些階段混淆在一起,另一個合約可以回撥到當前合約中,並修改多次執行的狀態或原因效果(以太付款). //如果內部調用的函數包含與外部合同的交互,則必須將它們視爲與外部合同的交互。 // 1.條件 require(now >= auctionEnd); //拍賣還沒有結束 require(!ended); //此功能已被調用 // 2.效果 ended = true; AuctionEnded(highestBidder, highestBid); // 3.交互 beneficiary.transfer(highestBid); }
}
盲拍 以前的公開拍賣會延伸到以下的盲拍。盲拍的優勢在於投標期結束時沒有時間壓力。在一個透明的計算平臺上創建一個盲目拍賣可能聽起來像是一個矛盾,但是密碼學可以解決這個問題。
在投標期間,投標人實際上並沒有發出她的投標,而只是一個散列版本。由於目前認爲實際上不可能找到兩個(足夠長)的哈希值相等的值,因此投標人承諾通過該投標。投標結束後,投標人必須公開他們的投標:他們將他們的價值未加密並且合同檢查散列值與投標期間提供的散列值相同。
另一個挑戰是如何在同一時間使拍賣具有約束力和盲目性:在贏得拍賣後,防止投標人不發送貨幣的唯一方法是讓她在拍賣中一併發送。由於價值轉移不能在以太坊矇蔽,任何人都可以看到價值。
以下合同通過接受任何大於最高出價的值來解決此問題。因爲這當然只能在披露階段進行檢查,所以有些出價可能是無效的,這是有意的(它甚至提供了一個明確的標記,用高價值轉讓放置無效出價):投標人可以通過放置幾個較高的低無效出價。
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 winnder, uint highestBid); ///修飾符是驗證函數輸入的便捷方式。 `onlyBefore`應用於下面的`bid`: // 新函數體是修飾符的主體,其中`_`被舊函數體替換。 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”不是true,則投標有效。 //將“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); } //這是一個“內部”功能,這意味着它只能從合同本身(或衍生合同)中調用。 function placeBid(address bidder, uint value) internal return (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); 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) { Aborted(); state = State.Inactive; seller.transfer(this.balance); } ///將購買確認爲買家。 /// Transaction必須包含`2 * value`以太。 ///以太會被鎖定,直到confirmReceived被調用。 function confirmPurchase() public inState(State.Created) condition(msg.value == (2 * value)) payable { PurchaseConfirmed(); buyer = msg.sender; state = State.Locked; } ///確認您(買家)收到該物品。 ///這將釋放鎖定的以太。 function confirmReceived() public onlyBuyer inState(State.Locked) { ItemReceived(); //先改變狀態很重要,否則使用下面的`send`調用的合約可以在這裏再次調用。 state = State.Inactive; //NOTE:這實際上允許買家和賣家阻止退款 - 應該使用退款模式。 buyer.transfer(value); seller.transfer(this.balance); }
}
微支付通道 To be written.