3. 安装Solidity
3.1 版本
官网建议下载最新版本的~
3.2 Remix
如果只是尝试编写Solidity的小合同,可以访问基于浏览器的Remix:https://remix.ethereum.org/
如果想在不连接互联网的情况下使用,可以访问https://github.com/ethereum/browser-solidity/tree/gh-pages并按照该页面上的说明下载.ZIP文件。
3.3 npm/Node.js
这可能安装Solidity到本地最轻便最省事的方法。
在基于浏览器的Solidity上,Emscripten提供了一个跨平台JavaScript库,把C++源码编译为JavaScript,同时也提供NPM安装包。它可以直接用于项目(如Remix)。 请参阅solc-js存储库以获取相关说明。
安装命令:
npm install -g solc
- 注:solcjs的命令行选项与solc和工具(如geth)不兼容。
3.4 二进制安装包
这部分略掉,什么系统的安装安装教程就可以了。
4. Solidity实例
4.1 投票(Voting)
这个合约比较复杂,但其中展示了很多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 < proposalNames.length; i++) {
// `Proposal({...})` 创建一个临时提案对象
// `proposals.push(...)`添加到`proposals`末尾。
proposals.push(Proposal({
name: proposalNames[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];
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;
// 我们在delegation中发现了一个不被允许的循环.
require(to != msg.sender);
}
// 因为 `sender` 是一个引用,
// 这里实际修改了`voters[msg.sender].voted`
sender.voted = true;
sender.delegate = to;
Voter storage delegate = voters[to];
if (delegate.voted) {
// 如果委托的投票代表已经投票了,
// d直接修改票数
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++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal = p;
}
}
}
// 调用winningProposal() 函数得到
// 包含在提案数组中获胜者的index
//并返回获胜者的名字
function winnerName() constant
returns (bytes32 winnerName)
{
winnerName = proposals[winningProposal()].name;
}
}
4.2 盲拍
在本节中,将展示在以太坊上创建盲拍合约是多么容易。 我们将从一个每个人都可以看到出价公开的拍卖开始,然后将这个合约扩展到一个在竞标期结束之前不可能看到实际的出价的盲拍。
4.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;
// 在结束时设置为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;
auctionEnd = now + _biddingTime;
}
/// 竞拍出价会随着交易事务一起发送
/// 只有在竞拍失败的时候才会退回
function bid() payable {
// 不需要任何参数,
// 所有的信息已经是
// 交易事务的一部分.
// 需要秘钥支付函数需要的以太币。
// 出价结束,撤销此调用
require(now <= auctionEnd);
// 如果出价不是最高的,
// 返回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)) {
// 不需要调用这里,重置金额就可以
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// 结束竞拍,发出最高的竞价给拍卖人(受益者)
function auctionEnd() {
// 这是一个很好的指导思想,可以分三个阶段来构建与其他契约交互的函数(即它们调用函数或者发送Ether)
// 1.检查条件
// 2.执行操作(潜在的变化条件)
//与其他合同交互
//如果这两个阶段混在一起,另一个合同可以回到当前的合同中,并修改多次执行的状态或原因效果(以太付款)。如果内部调用的功能包括与外部合同的交互,他们还必须被视为与外部合同的交互。
// 1. 条件
require(now >= auctionEnd); // auction did not yet end
require(!ended); //这个函数已经被调用
// 2. 效果
ended = true;
AuctionEnded(highestBidder, highestBid);
// 3. 交互作用
beneficiary.transfer(highestBid);
}
}
4.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;
event AuctionEnded(address winner, uint highestBid);
/// 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[] _values,
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);
}
// This is an "internal" function which means that it
// can only be called from the contract itself (or from
// derived contracts).
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) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `transfer` returns (see the remark above about
// conditions -> effects -> interaction).
pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
///竞拍结束后发送最高出价到竞拍人
function auctionEnd()
onlyAfter(revealEnd)
{
require(!ended);
AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
}
4.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 == seller);
_;
}
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);
}
}