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);
}
}