接下來的合約非常復(fù)雜,但展示了很多Solidity的特性。它實現(xiàn)了一個投票合約。當然,電子選舉的主要問題是如何賦予投票權(quán)給準確的人,并防止操縱。我們不能解決所有的問題,但至少我們會展示如何委托投票可以同時做到投票統(tǒng)計是自動和完全透明。
思路是為每張選票創(chuàng)建一個合約,每個投票選項提供一個短名稱。合約創(chuàng)建者作為會長將會給每個投票參與人各自的地址投票權(quán)。
地址后面的人們可以選擇自己投票或者委托信任的代表人替他們投票。在投票結(jié)束后,winningProposal()將會返回獲得票數(shù)最多的提案。
/// @title Voting with delegation.
/// @title 授權(quán)投票
contract Ballot
{
// 這里聲明了復(fù)雜類型
// 將會在被后面的參數(shù)使用
// 代表一個獨立的投票人。
struct Voter
{
uint weight; // 累積的權(quán)重。
bool voted; // 如果為真,則表示該投票人已經(jīng)投票。
address delegate; // 委托的投票代表
uint vote; // 投票選擇的提案索引號
}
// 這是一個獨立提案的類型
struct Proposal
{
bytes32 name; // 短名稱(32字節(jié))
uint voteCount; // 累計獲得的票數(shù)
}
address public chairperson;
//這里聲明一個狀態(tài)變量,保存每個獨立地址的`Voter` 結(jié)構(gòu)
mapping(address => Voter) public voters;
//一個存儲`Proposal`結(jié)構(gòu)的動態(tài)數(shù)組
Proposal[] public proposals;
// 創(chuàng)建一個新的投票用于選出一個提案名`proposalNames`.
function Ballot(bytes32[] proposalNames)
{
chairperson = msg.sender;
voters[chairperson].weight = 1;
//對提供的每一個提案名稱,創(chuàng)建一個新的提案
//對象添加到數(shù)組末尾
for (uint i = 0; i < proposalNames.length; i++)
//`Proposal({...})` 創(chuàng)建了一個臨時的提案對象,
//`proposal.push(...)`添加到了提案數(shù)組`proposals`末尾。
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
//給投票人`voter`參加投票的投票權(quán),
//只能由投票主持人`chairperson`調(diào)用。
function giveRightToVote(address voter)
{
if (msg.sender != chairperson || voters[voter].voted)
//`throw`會終止和撤銷所有的狀態(tài)和以太改變。
//如果函數(shù)調(diào)用無效,這通常是一個好的選擇。
//但是需要注意,這會消耗提供的所有g(shù)as。
throw;
voters[voter].weight = 1;
}
// 委托你的投票權(quán)到一個投票代表 `to`。
function delegate(address to)
{
// 指定引用
Voter sender = voters[msg.sender];
if (sender.voted)
throw;
//當投票代表`to`也委托給別人時,尋找到最終的投票代表
while (voters[to].delegate != address(0) &&
voters[to].delegate != msg.sender)
to = voters[to].delegate;
// 當最終投票代表等于調(diào)用者,是不被允許的。
if (to == msg.sender)
throw;
//因為`sender`是一個引用,
//這里實際修改了`voters[msg.sender].voted`
sender.voted = true;
sender.delegate = to;
Voter delegate = voters[to];
if (delegate.voted)
//如果委托的投票代表已經(jīng)投票了,直接修改票數(shù)
proposals[delegate.vote].voteCount += sender.weight;
else
//如果投票代表還沒有投票,則修改其投票權(quán)重。
delegate.weight += sender.weight;
}
///投出你的選票(包括委托給你的選票)
///給 `proposals[proposal].name`。
function vote(uint proposal)
{
Voter sender = voters[msg.sender];
if (sender.voted) throw;
sender.voted = true;
sender.vote = proposal;
//如果`proposal`索引超出了給定的提案數(shù)組范圍
//將會自動拋出異常,并撤銷所有的改變。
proposals[proposal].voteCount += sender.weight;
}
///@dev 根據(jù)當前所有的投票計算出當前的勝出提案
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;
}
}
}
}
現(xiàn)在,指派投票權(quán)到所有的投票參加者需要許多的交易。你能想出更好的方法么?
這一節(jié),我們將展示在以太上創(chuàng)建一個完整的盲拍合約是多么簡單。我們從一個所有人都能看到出價的公開拍賣開始,接著擴展合約成為一個在拍賣結(jié)束以前不能看到實際出價的盲拍。
通常簡單的公開拍賣合約,是每個人可以在拍賣期間發(fā)送他們的競拍出價。為了實現(xiàn)綁定競拍人的到他們的拍賣,競拍包括發(fā)送金額/ether。如果產(chǎn)生了新的最高競拍價,前一個最高價競拍人將會拿回他的錢。在競拍階段結(jié)束后,受益人人需要手動調(diào)用合約收取他的錢 — — 合約不會激活自己。
contract SimpleAuction {
// 拍賣的參數(shù)。
// 時間要么為unix絕對時間戳(自1970-01-01以來的秒數(shù)),
// 或者是以秒為單位的出塊時間
address public beneficiary;
uint public auctionStart;
uint public biddingTime;
//當前的拍賣狀態(tài)
address public highestBidder;
uint public highestBid;
//在結(jié)束時設(shè)置為true來拒絕任何改變
bool ended;
//當改變時將會觸發(fā)的Event
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
//下面是一個叫做natspec的特殊注釋,
//由3個連續(xù)的斜杠標記,當詢問用戶確認交易事務(wù)時將顯示。
///創(chuàng)建一個簡單的合約使用`_biddingTime`表示的競拍時間,
/// 地址`_beneficiary`.代表實際的拍賣者
function SimpleAuction(uint _biddingTime,
address _beneficiary) {
beneficiary = _beneficiary;
auctionStart = now;
biddingTime = _biddingTime;
}
///對拍賣的競拍保證金會隨著交易事務(wù)一起發(fā)送,
///只有在競拍失敗的時候才會退回
function bid() {
//不需要任何參數(shù),所有的信息已經(jīng)是交易事務(wù)的一部分
if (now > auctionStart + biddingTime)
//當競拍結(jié)束時撤銷此調(diào)用
throw;
if (msg.value <= highestBid)
//如果出價不是最高的,發(fā)回競拍保證金。
throw;
if (highestBidder != 0)
highestBidder.send(highestBid);
highestBidder = msg.sender;
highestBid = msg.value;
HighestBidIncreased(msg.sender, msg.value);
}
///拍賣結(jié)束后發(fā)送最高的競價到拍賣人
function auctionEnd() {
if (now <= auctionStart + biddingTime)
throw;
//拍賣還沒有結(jié)束
if (ended)
throw;
//這個收款函數(shù)已經(jīng)被調(diào)用了
AuctionEnded(highestBidder, highestBid);
//發(fā)送合約擁有所有的錢,因為有一些保證金可能退回失敗了。
beneficiary.send(this.balance);
ended = true;
}
function () {
//這個函數(shù)將會在發(fā)送到合約的交易事務(wù)包含無效數(shù)據(jù)
//或無數(shù)據(jù)的時執(zhí)行,這里撤銷所有的發(fā)送,
//所以沒有人會在使用合約時因為意外而丟錢。
throw;
}
}
接下來擴展前面的公開拍賣成為一個盲拍。盲拍的特點是拍賣結(jié)束以前沒有時間壓力。在一個透明的計算平臺上創(chuàng)建盲拍系統(tǒng)聽起來可能有些矛盾,但是加密算法能讓你脫離困境。
在拍賣階段, 競拍人不需要發(fā)送實際的出價,僅僅只需要發(fā)送一個它的散列值。因為目前幾乎不可能找到兩個值(足夠長)的散列值相等,競拍者提交他們的出價散列值。在拍賣結(jié)束后,競拍人重新發(fā)送未加密的競拍出價,合約將檢查其散列值是否和拍賣階段發(fā)送的一樣。 另一個挑戰(zhàn)是如何讓拍賣同時實現(xiàn)綁定和致盲 :防止競拍人競拍成功后不付錢的唯一的辦法是,在競拍出價的同時發(fā)送保證金。但是在Ethereum上發(fā)送保證金是無法致盲,所有人都能看到保證金。下面的合約通過接受任何盡量大的出價來解決這個問題。當然這可以在最后的揭拍階段進行復(fù)核,一些競拍出價可能是無效的,這樣做的目的是(它提供一個顯式的標志指出是無效的競拍,同時包含高額保證金):競拍人可以通過放置幾個無效的高價和低價競拍來混淆競爭對手。
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);
///修飾器(Modifier)是一個簡便的途徑用來驗證函數(shù)輸入的有效性。
///`onlyBefore` 應(yīng)用于下面的 `bid`函數(shù),其舊的函數(shù)體替換修飾器主體中 `_`后就是其新的函數(shù)體
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;
}
///放置一個盲拍出價使用`_blindedBid`=sha3(value,fake,secret).
///僅僅在競拍結(jié)束正常揭拍后退還發(fā)送的以太。當隨同發(fā)送的以太至少
///等于 "value"指定的保證金并且 "fake"不為true的時候才是有效的競拍
///出價。設(shè)置 "fake"為true或發(fā)送不合適的金額將會掩沒真正的競拍出
///價,但是仍然需要抵押保證金。同一個地址可以放置多個競拍。
function bid(bytes32 _blindedBid)
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: _blindedBid,
deposit: msg.value
}));
}
///揭開你的盲拍競價。你將會拿回除了最高出價外的所有競拍保證金
///以及正常的無效盲拍保證金。
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))
//出價未被正常揭拍,不能取回保證金。
continue;
refund += bid.deposit;
if (!fake && bid.deposit >= value)
if (placeBid(msg.sender, value))
refund -= value;
//保證發(fā)送者絕不可能重復(fù)取回保證金
bid.blindedBid = 0;
}
msg.sender.send(refund);
}
//這是一個內(nèi)部 (internal)函數(shù),
//意味著僅僅只有合約(或者從其繼承的合約)可以調(diào)用
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid)
return false;
if (highestBidder != 0)
//退還前一個最高競拍出價
highestBidder.send(highestBid);
highestBid = value;
highestBidder = bidder;
return true;
}
///競拍結(jié)束后發(fā)送最高出價到競拍人
function auctionEnd()
onlyAfter(revealEnd)
{
if (ended) throw;
AuctionEnded(highestBidder, highestBid);
//發(fā)送合約擁有所有的錢,因為有一些保證金退回可能失敗了。
beneficiary.send(this.balance);
ended = true;
}
function () { throw; }
}
Safe Remote Purchase 安全的遠程購物
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();
///終止購物并收回以太。僅僅可以在合約未鎖定時被賣家調(diào)用。
function abort()
onlySeller
inState(State.Created)
{
aborted();
seller.send(this.balance);
state = State.Inactive;
}
///買家確認購買。交易包含兩倍價值的(`2 * value`)以太。
///這些以太會一直鎖定到收貨確認(confirmReceived)被調(diào)用。
function confirmPurchase()
inState(State.Created)
require(msg.value == 2 * value)
{
purchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}
///確認你(買家)收到了貨物,這將釋放鎖定的以太。
function confirmReceived()
onlyBuyer
inState(State.Locked)
{
itemReceived();
buyer.send(value);//我們有意忽略了返回值。
seller.send(this.balance);
state = State.Inactive;
}
function() { throw; }
}
小額支付通道
待補