智慧合約開發(4)—— solidity例項
1.投票
這個合約比較複雜,但其中展示了很多的Solidity的特性。它實現了一個投票合約。電子投票的主要問題是如何給正確的人分配投票權,以及如何防止操縱。我們不會在這裡解決所有問題,但我們會展示如何進行委派投票,以便記票自動且完全透明。
idea 是為每個選票建立一個合約,為每個投票提供一個短名稱。合同的創造者作為主席將分別給予每個地址投票權。
地址背後的投票人可以選擇他們自己進行投票或者委託投票權給他們信任的人。
投票時間結束的時候,winningProposal()將會返回獲得票數最多的提案。
pragma solidity ^0.4.11;
//@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 < proposalName.length; i++){
// 'Proposal({...})' 建立一個臨時提案物件
// ‘proposals.push(...)' 新增到’proposal‘ 末尾
proposals.push(Proposal({
name:proposalName[i],
voteCount: 0
}));
}
}
// 給投票人’voter‘投票權
// 只能由主席’ chairperson' 呼叫
function giveRightToVote(address voter) {
// 如果 ‘ require‘ 計算結果為 ’false‘
// 他將終止並返回所有更改
// 返回之前的狀態和以太幣
require((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0));
voters[voter].weight = 1;
}
//委託你的投票權到一個投票代表’to‘
function delegate(address to){
// 指定引用
Voter storage sender = voters[msg.sender];//這裡是引用的用法,sender表示記憶體中的這個結構
require(!sender.voted);//這句話是說這個人不能已經投過票了
// 不允許自己委託自己(不允許委託為呼叫者)。
require(to != msg.sender);
// Forward the delegation as long as
// 'to' also delegated
// 一般來說,這樣的迴圈是非常危險的
// 因為如果它們執行時間太長
// 它們可能需要比block中可用的更多的gas
// 在這種情況下,delegation不會被執行
// 但在其他情況下,這樣的迴圈
// 可能導致合約完全“卡住”
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
//我們在dele中發現了一個不被允許的迴圈
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) {
Voter storage sender = voters[msg.sender];
require(!sender.voted);
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++){
winningVoteCount = proposals[p].voteCount;
winningProposal = p;
}
}
}
//呼叫WinningProposal() 函式得到
// 包含在提案陣列中獲勝者的index
// 並返回獲勝者的名字
function winnerName() constant returns (butes32 winnerName)
{
winnerName = proposals[winningProposal()].name;
}
}
2 . 盲拍
在本節中,將展示在以太坊上建立盲拍合約是多麼容易。我們將從一個每個人都可以看到出價公開的拍賣開始,然後將這個合約擴充套件到一個在競標期結束之前不可能看到實際的出價的盲拍。
2.1 一個簡單的公開拍賣
以下簡單拍賣合約的總體思路是每個人都可以在拍賣期間內發出他們的出價。為了保證競拍人得到他們的競拍,出價包括阿傳送金額/ether。如果出現了新的最高出價,之前的最高出價這就可以拿回她的錢了。在競拍期結束後,受益人必須手動呼叫合約收取他的錢——合約不能啟用自己。
pragma solidity ^0.4.11
contract SimpleAuction{
// 拍賣的引數
// 時間是unix絕對時間戳(自1970-01-01 以來的秒數)
// 或者是以秒為單位的出塊時間
address public beneficiary;
uint public auctionEnd;
// 當前的拍賣狀態
address public highestBidder;
uint public highestBid;
// 允許撤回之前的競拍
mapping(address => uint) pendingReturns;
// 在結束時設定為trrue在拒絕任何改變
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;
auctionEnd = now + _biddingTime;
}
/// 競拍出價會隨著交易事務一起傳送
/// 只有在競拍失敗的時候才會退回
function bid() payable{
// 不需要任何引數
// 所有資訊已經是交易事務的一部分。
// 需要祕鑰支付函式需要的以太幣
// 出價結束,撤銷該呼叫
require (now <= auctionEnd);//Time
// 如果出價不是最高的
// 返回money
require(msg.value > highestBid);
if (highestBidder != 0){
// 用簡單的方式把錢寄回來
// highestBidder.send(highestBid) 是一個不安全的因素
// 因為他可以執行不受信任的合約
// 讓收款人自己收回錢總是比較安全的
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
HighestBidIncreased(msg.sender,msg.value);
}
/// 撤回出價過高的競拍
function withdraw() returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// 把這個設定為0很重要
// 因為收件人可以再次呼叫這個函式
// 在"send"返回之前作為接受呼叫的一部分
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) {
// 不需要呼叫這裡,重置金額就行
pendongReturns[msg.sender] = amount;
return false;
}//這裡邏輯大致是,如果呼叫者收到退款,退款額就退0;沒有收到就重置為原退款額
}
return true;
}
/// 結束競拍,發出最高的競價給拍賣人(受益者)
function auctionEnd(){
// 這是一個很好的指導思想,可以分三個階段來構建與其他契約互動的函式(即它們呼叫函式或者傳送Ether)
// 1.檢查條件
// 2.執行操作(潛在的變化條件)
// 3.與其他合同互動
// 如果這兩個階段混在一起,另一個合同可以回到當前的合同中,並修改多次執行的狀態或原因效果(以太付款)
// 如果內部呼叫的功能包括與外部合同的互動,他們還必須被視為與外部合同的互動.
// 1.條件
require(now >= auctionEnd);// auction didn't end yet(原教程這麼說,但感覺更像是競拍已結束啊..)
require(!ended);//這個函式已被呼叫
// 2. 效果
ended = true;
AuctionEnded(highestBidder, highestBid);
// 3. 互動作用
beneficiary.transfer(highestBid);
}
}
2.2 盲拍
從上面公開拍賣的例子延伸到下面的例子:盲拍。盲拍的好處:招標期結束時沒有時間壓力(感覺像個病句。。)。利用密碼學可以實現在一個透明的計算平臺上建立一個盲拍。
在投標期間,投標人實際上並沒有發出投標書,而只是一個被hash過的值。由於目前認為實際上不可能找到兩個(足夠長的)雜湊值相等的值,所以投標人通過提交hash值來投標。在投標期結束後,投標人必須公開投標:未加密的傳送其價格。並且合同檢查hash值與投標期間提供的hash值是否相同。
一下合約通過接受任何大於最高出價的值來解決問題。因為只能在揭牌階段(最後傳送價格時)進行檢查,所以有些出價可能是無效的,這樣做的目的是(它提供一個顯示的標誌指出該競拍是無效的,同時包含高額的保證金):競拍人可以通過防止幾個無效的高價和低價競拍來混淆競爭對手。
pragma solidity ^0.4.11;
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;
/// Modifiers 是一個驗證函式輸入的方便方法
/// 'onlyBefore' 被應用到下面的'bid':
/// The new function body is the modifier's body where
/// '_' is replaced by the old function body.
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }
// 修飾器,作用是放在函式前面,只有先執行修飾器的程式,在執行後面的,原函式的步驟相等與在_位置
function BlindAuction(
uint _biddingTime,
uint _revealTime,
address _beneficiary
){
beneficiary = _beneficiary;
biddingEnd = now + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}// 設定一個盲拍
/// 放置一個盲拍出價使用 '_blindedBid' = keccak256(value,fake,secret).
/// 傳送的ether僅僅只在競拍結束的揭拍後退還競價正確的ether
/// 有效的投標是在投標是附帶大於等於"value"的ether並且"fake" 不為true
/// 設定"fake"為true或傳送不合適的金額將會淹沒真正的競拍出價,但是仍然需要抵押保證金
/// 同一個地址可以放置多個競拍
function bid(bytes32 _blindedBid)
payable
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[] _value,
bool[] _fake,
bytes32[] _secret
)
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"函式表示的是它僅僅能被合約本身喚醒(或derived contract)
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(){
uint amount = pendingReturns[msg.sender];
if (amount > 0){
pendingReturns[msg.sender] = 0;
msg.sender.transfei(amount);
}
}
/// 競拍結束後傳送最高出價到競拍人
function auctionEnd()
onlyAfter(revealEnd)
{
require(!ended);
AuctionEnded(highestBIdder,highestBid);
ended = true;
beneficiary.transfei(highestBid);
}
}
3.安全的遠端購買
pragma solidity ^0.4.11;
contract Purchase{
uint public value;
address public seller;
address public buyer;
enum State { Created,Locked,Inactive }
State public state;
// 確保 'msg.value' 為偶數
// 如果是奇數, 使用除法
// 通過乘法檢查, 它不是一個奇數
function Purchase() 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 == selller);
_;
}
modifier inState(State _state) {
require(state == _state);
_;
}
event Aborted();// 失敗
event PurchaseConfirmed();// 購買確認
event ItemReceived();// 專案獲得
/// 終止購買並回收ether
/// 在合約被鎖之前可被購買者回調
function abort()
onlySeller
inState(State.Created)
{
Aborted();
state = State.Inactive;
seller.transfer(this.balance);
}
/// 作為買方確認購買
/// 事務必須包括 ' 2 * value' ether
/// ether 被鎖直到確認收貨被回撥
function confirmPurchase()
inState(State.Created)
condition(msg.value == (2 * value))
payable
{
PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}
/// 確認買家收到貨
/// 然後釋放被鎖的ether
function confirmReceived()
onlyBuyer
inState(State.Locked)
{
ItemReceived();
// 首先改變狀態很重要,否則合約可以使用'using'被再次呼叫
state = State.Inactive;
// NOTE: 實際上允許買方和賣方阻止退款——撤回模式可以使用
buyer.transfer(value);
seller.transfer(this.balance);
}
}