Solidity 文件--第三章:Solidity 程式設計例項
Solidity 程式設計例項
Voting 投票
接下來的合約非常複雜,但展示了很多Solidity的特性。它實現了一個投票合約。當然,電子選舉的主要問題是如何賦予投票權給準確的人,並防止操縱。我們不能解決所有的問題,但至少我們會展示如何委託投票可以同時做到投票統計是自動和完全透明。
思路是為每張選票建立一個合約,每個投票選項提供一個短名稱。合約建立者作為會長將會給每個投票參與人各自的地址投票權。
地址後面的人們可以選擇自己投票或者委託信任的代表人替他們投票。在投票結束後,winningProposal()將會返回獲得票數最多的提案。
/// @title Voting with delegation.
/// @title 授權投票
contract Ballot
{
// 這裡聲明瞭複雜型別
// 將會在被後面的引數使用
// 代表一個獨立的投票人。
struct Voter
{
uint weight; // 累積的權重。
bool voted; // 如果為真,則表示該投票人已經投票。
address delegate; // 委託的投票代表
uint vote; // 投票選擇的提案索引號
}
// 這是一個獨立提案的型別
struct Proposal
{
bytes32 name; // 短名稱(32位元組)
uint voteCount; // 累計獲得的票數
}
address public chairperson;
//這裡宣告一個狀態變數,儲存每個獨立地址的`Voter` 結構
mapping(address => Voter) public voters;
//一個儲存`Proposal`結構的動態陣列
Proposal[] public proposals;
// 建立一個新的投票用於選出一個提案名`proposalNames`.
function Ballot(bytes32[] proposalNames)
{
chairperson = msg.sender;
voters[chairperson].weight = 1;
//對提供的每一個提案名稱,建立一個新的提案
//物件新增到陣列末尾
for (uint i = 0; i < proposalNames.length; i++)
//`Proposal({...})` 建立了一個臨時的提案物件,
//`proposal.push(...)`新增到了提案陣列`proposals`末尾。
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
//給投票人`voter`參加投票的投票權,
//只能由投票主持人`chairperson`呼叫。
function giveRightToVote(address voter)
{
if (msg.sender != chairperson || voters[voter].voted)
//`throw`會終止和撤銷所有的狀態和以太改變。
//如果函式呼叫無效,這通常是一個好的選擇。
//但是需要注意,這會消耗提供的所有gas。
throw;
voters[voter].weight = 1;
}
// 委託你的投票權到一個投票代表 `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;
// 當最終投票代表等於呼叫者,是不被允許的。
if (to == msg.sender)
throw;
//因為`sender`是一個引用,
//這裡實際修改了`voters[msg.sender].voted`
sender.voted = true;
sender.delegate = to;
Voter delegate = voters[to];
if (delegate.voted)
//如果委託的投票代表已經投票了,直接修改票數
proposals[delegate.vote].voteCount += sender.weight;
else
//如果投票代表還沒有投票,則修改其投票權重。
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`索引超出了給定的提案陣列範圍
//將會自動丟擲異常,並撤銷所有的改變。
proposals[proposal].voteCount += sender.weight;
}
///@dev 根據當前所有的投票計算出當前的勝出提案
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;
}
}
}
}
可能的改進
現在,指派投票權到所有的投票參加者需要許多的交易。你能想出更好的方法麼?
盲拍
這一節,我們將展示在以太上建立一個完整的盲拍合約是多麼簡單。我們從一個所有人都能看到出價的公開拍賣開始,接著擴充套件合約成為一個在拍賣結束以前不能看到實際出價的盲拍。
簡單的公開拍賣
通常簡單的公開拍賣合約,是每個人可以在拍賣期間傳送他們的競拍出價。為了實現繫結競拍人的到他們的拍賣,競拍包括髮送金額/ether。如果產生了新的最高競拍價,前一個最高價競拍人將會拿回他的錢。在競拍階段結束後,受益人人需要手動呼叫合約收取他的錢 — — 合約不會啟用自己。
contract SimpleAuction {
// 拍賣的引數。
// 時間要麼為unix絕對時間戳(自1970-01-01以來的秒數),
// 或者是以秒為單位的出塊時間
address public beneficiary;
uint public auctionStart;
uint public biddingTime;
//當前的拍賣狀態
address public highestBidder;
uint public highestBid;
//在結束時設定為true來拒絕任何改變
bool ended;
//當改變時將會觸發的Event
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
//下面是一個叫做natspec的特殊註釋,
//由3個連續的斜槓標記,當詢問使用者確認交易事務時將顯示。
///建立一個簡單的合約使用`_biddingTime`表示的競拍時間,
/// 地址`_beneficiary`.代表實際的拍賣者
function SimpleAuction(uint _biddingTime,
address _beneficiary) {
beneficiary = _beneficiary;
auctionStart = now;
biddingTime = _biddingTime;
}
///對拍賣的競拍保證金會隨著交易事務一起傳送,
///只有在競拍失敗的時候才會退回
function bid() {
//不需要任何引數,所有的資訊已經是交易事務的一部分
if (now > auctionStart + biddingTime)
//當競拍結束時撤銷此呼叫
throw;
if (msg.value <= highestBid)
//如果出價不是最高的,發回競拍保證金。
throw;
if (highestBidder != 0)
highestBidder.send(highestBid);
highestBidder = msg.sender;
highestBid = msg.value;
HighestBidIncreased(msg.sender, msg.value);
}
///拍賣結束後傳送最高的競價到拍賣人
function auctionEnd() {
if (now <= auctionStart + biddingTime)
throw;
//拍賣還沒有結束
if (ended)
throw;
//這個收款函式已經被呼叫了
AuctionEnded(highestBidder, highestBid);
//傳送合約擁有所有的錢,因為有一些保證金可能退回失敗了。
beneficiary.send(this.balance);
ended = true;
}
function () {
//這個函式將會在傳送到合約的交易事務包含無效資料
//或無資料的時執行,這裡撤銷所有的傳送,
//所以沒有人會在使用合約時因為意外而丟錢。
throw;
}
}
Blind Auction 盲拍
接下來擴充套件前面的公開拍賣成為一個盲拍。盲拍的特點是拍賣結束以前沒有時間壓力。在一個透明的計算平臺上建立盲拍系統聽起來可能有些矛盾,但是加密演算法能讓你脫離困境。
在拍賣階段, 競拍人不需要傳送實際的出價,僅僅只需要傳送一個它的雜湊值。因為目前幾乎不可能找到兩個值(足夠長)的雜湊值相等,競拍者提交他們的出價雜湊值。在拍賣結束後,競拍人重新發送未加密的競拍出價,合約將檢查其雜湊值是否和拍賣階段傳送的一樣。 另一個挑戰是如何讓拍賣同時實現繫結和致盲 :防止競拍人競拍成功後不付錢的唯一的辦法是,在競拍出價的同時傳送保證金。但是在Ethereum上傳送保證金是無法致盲,所有人都能看到保證金。下面的合約通過接受任何儘量大的出價來解決這個問題。當然這可以在最後的揭拍階段進行復核,一些競拍出價可能是無效的,這樣做的目的是(它提供一個顯式的標誌指出是無效的競拍,同時包含高額保證金):競拍人可以通過放置幾個無效的高價和低價競拍來混淆競爭對手。
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)是一個簡便的途徑用來驗證函式輸入的有效性。
///`onlyBefore` 應用於下面的 `bid`函式,其舊的函式體替換修飾器主體中 `_`後就是其新的函式體
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).
///僅僅在競拍結束正常揭拍後退還發送的以太。當隨同傳送的以太至少
///等於 "value"指定的保證金並且 "fake"不為true的時候才是有效的競拍
///出價。設定 "fake"為true或傳送不合適的金額將會掩沒真正的競拍出
///價,但是仍然需要抵押保證金。同一個地址可以放置多個競拍。
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;
//保證傳送者絕不可能重複取回保證金
bid.blindedBid = 0;
}
msg.sender.send(refund);
}
//這是一個內部 (internal)函式,
//意味著僅僅只有合約(或者從其繼承的合約)可以呼叫
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;
}
///競拍結束後傳送最高出價到競拍人
function auctionEnd()
onlyAfter(revealEnd)
{
if (ended) throw;
AuctionEnded(highestBidder, highestBid);
//傳送合約擁有所有的錢,因為有一些保證金退回可能失敗了。
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();
///終止購物並收回以太。僅僅可以在合約未鎖定時被賣家呼叫。
function abort()
onlySeller
inState(State.Created)
{
aborted();
seller.send(this.balance);
state = State.Inactive;
}
///買家確認購買。交易包含兩倍價值的(`2 * value`)以太。
///這些以太會一直鎖定到收貨確認(confirmReceived)被呼叫。
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; }
}
小額支付通道
待補