Solidity 官方文档中文版
官方文档中文版
欢迎各路区块链及以太坊领域的专家和爱好者参与这一翻译项目,我们会为每位翻译和校对人员署名。
特别介绍
特别介绍
Solidity 官方文档前一版已经翻译完成90%,内容需要更新。
前一版贡献者名单:
少平
abao
志伟
jinfengbc
大青蛙
zhangyaning
一起来参与
一起来参与
如果想做出贡献(翻译或者校对)的话,请加QQ群:195819423,谢谢!
PS: 想探讨Solidity技术的可以加"Solidity技术交流群":549293015
内容来源
内容来源
英文官方网站:
https://solidity.readthedocs.io/
官方GitHub仓库:
https://github.com/ethereum/solidity
中文版 GitHub 仓库:
https://github.com/twq0076262/solidity-zh
参与步骤
参与步骤
fork主仓库(https://github.com/twq0076262/solidity-zh)
按照章节认领翻译(每次申请一个章节)或者校对(可申请多个章节)在下面这个README.md 里找还没有被人申请的章节,写上(@你的github号),
给主仓库的master 分支提pull request;
提的 pull request 被确认,合并到主仓库后,代表你申请的章节认领完成,开始翻译或校对;
翻译或校对的文件为README.md或者TOC.md中对应的md文件,请不要翻译/校对单独文件夹中的index.md
翻译过程请参照 翻译协作规范 (见下一节),完成翻译后提交 pull request 给主仓库的master 分支;
完成校对后同样提交 pull request 给主仓库的master 分支;
全部翻译/校对完成后,我们会生成PDF/ePub文档,放在极客学院Wiki平台发布,并为所有参与者署名!
翻译协作规范
翻译协作规范
为了让大家协作顺畅,需要每一个人遵循如下协作规范~
如果对Markdown和GitHub不了解,请先阅读如何使用Markdown以及如何使用GitHub
使用Markdown进行翻译,文件名必须使用英文
翻译后的文档请放到SOURCE文件夹下的对应章节中,然后pull request即可
如遇到文中的图片,请统一放在SOURCE/images目录下
原文中的HTML标签及代码请不要修改、翻译
有其他任何问题都欢迎发issue,我们看到了会尽快回复
翻译人员需将对应的原文地址和翻译人姓名添加到译文末尾,审校人员需要将自己的名字添加到译文末尾,具体格式请参见样例:
原文:Color Palettes 翻译:iceskysl 校对:PoppinLp
校对规范
校对规范
认领校对时请提供相关方面的专业背景说明,保证校对质量
文章通顺,适合人类阅读与理解,别让人以为是机翻
确保图片都能正常显示,且其相对地址都是SOURCE/images,图片存放正确
专有名词符合术语表中的翻译要求,拿不准的新词汇可以使用中文翻译后加括号内英文的形式
校对人员需有一定专业背景,保证译文无专业知识方面错误
请查看翻译人员是否将原文链接和翻译链接放到译文最下方,审校人员需要将自己的名字添加到译文末尾,具体格式请参见样例:
原文:Color Palettes 翻译:iceskysl 校对:PoppinLp
有任何问题请提Issues或到协同翻译群讨论,校对后提PR等待Merge,管理员通过后会在目录后面打√标识完成
参与者(按认领章节排序)
参与者(按认领章节排序)
翻译翻译 & 校对校对
智能合约介绍 翻译:(待认领)校对: (待认领)
安装Solidity 翻译:(待认领)校对: (待认领)
Solidity 编程实例 翻译:(待认领)校对: (待认领)
深入理解 Solidity 翻译:(待认领)校对: (待认领)
源文件的布局 翻译:(待认领)校对: (待认领)
合约的结构 翻译:(待认领)校对: (待认领)
类型 翻译:(待认领)校对: (待认领)
单位和全局可用变量 翻译:(待认领)校对: (待认领)
表达式和控制结构 翻译:(待认领)校对: (待认领)
合约 翻译:(待认领)校对: (待认领)
杂项 翻译:(待认领)校对: (待认领)
Security Considerations翻译:(待认领)校对: (待认领)
编程规范 翻译:(待认领)校对: (待认领)
通用模式 翻译:(待认领)校对: (待认领)
常见问题 翻译:(待认领)校对: (待认领)
进度记录
进度记录
2016-8-26, 极客学院Wiki启动协同翻译,创建 GitHub 仓库,制定协同规范
感谢支持
感谢支持
离线版本
离线版本
简介简介
Solidity是一种语法类似JavaScript的高级语言。它被设计成以编译的方式生成以太坊虚拟机代码。在后续内容中你将会发现,使用它很容易创建用于投
票、众筹、封闭拍卖、多重签名钱包等等的合约。
注意注意
目前尝试Solidity的最好方式是使用基于浏览器的编译器(需要一点时间加载,请耐心等待)。
有用链接
Ethereum
Browser-Based CompilerChangelog
Story Backlog
Source Code
Gitter Chat
Solidity 文档文档
在下一章中,我们先看一个用Solidity写的简单的智能合约,然后介绍一下区块链和以太坊虚拟机的基础知识。
后续章节会通过一些实用的合约例子,来探索Solidity的一系列特性。记住,你可以在浏览器中尝试这些合约。
最后以及更多扩展章节的内容,会深入到Solidity 的各个方面。
如有任何关于Solidiy,或者本文档的问题及改进建议,请在gitter频道提出来。
简介
智能合约介绍
安装Solidity
Solidity 编程实例
深入理解 Solidity
源文件的布局
合约的结构
类型
单位和全局可用变量
表达式和控制结构
合约
杂项
Security Considerations
编程规范
通用模式
常见问题
智能合约介绍
智能合约介绍
一个简单的智能合约
一个简单的智能合约
先从一个非常基础的例子开始,不用担心你现在还一点都不了解,我们将逐步了解到更多的细节。
Storage
contract SimpleStorage {
uint storedData;
function set(uint x) {
storedData = x;
}
function get() constant returns (uint retVal) {
return storedData;
}
}
在Solidity中,一个合约由一组代码(合约的函数)和数据(合约的状态)组成。合约位于以太坊区块链上的一个特殊地址。uint storedData; 这行代码
声明了一个状态变量,变量名为storedData,类型为 uint (256bits无符号整数)。你可以认为它就像数据库里面的一个存储单元,跟管理数据库一
样,可以通过调用函数查询和修改它。在以太坊中,通常只有合约的拥有者才能这样做。在这个例子中,函数 set 和 get 分别用于修改和查询变量的
值。
跟很多其他语言一样,访问状态变量时,不需要在前面增加 this. 这样的前缀。
这个合约还无法做很多事情(受限于以太坊的基础设施),仅仅是允许任何人储存一个数字。而且世界上任何一个人都可以来存取这个数字,缺少一
个(可靠的)方式来保护你发布的数字。任何人都可以调用set方法设置一个不同的数字覆盖你发布的数字。但是你的数字将会留存在区块链的历史
上。稍后我们会学习如何增加一个存取限制,使得只有你才能修改这个数字。
代币的例子
代币的例子
接下来的合约将实现一个形式最简单的加密货币。空中取币不再是一个魔术,当然只有创建合约的人才能做这件事情(想用其他货币发行模式也很简
单,只是实现细节上的差异)。而且任何人都可以发送货币给其他人,不需要注册用户名和密码,只要有一对以太坊的公私钥即可。
Note
对于在线solidity环境来说,这不是一个好的例子。如果你使用在线solidity环境 来尝试这个例子。调用函数时,将无法改变from的地址。所以你只能扮
演铸币者的角色,可以铸造货币并发送给其他人,而无法扮演其他人的角色。这点在线solidity环境将来会做改进。
contract Coin {
//关键字“public”使变量能从合约外部访问。
address public minter;
mapping (address => uint) public balances;
//事件让轻客户端能高效的对变化做出反应。
event Sent(address from, address to, uint amount);
//这个构造函数的代码仅仅只在合约创建的时候被运行。
function Coin() {
minter = msg.sender;
}
function mint(address receiver, uint amount) {
if (msg.sender != minter) return;
balances[receiver] += amount;
}
function send(address receiver, uint amount) {
if (balances[msg.sender] < amount) return;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Sent(msg.sender, receiver, amount);
}
}
这个合约引入了一些新的概念,让我们一个一个来看一下。
address public minter; 这行代码声明了一个可公开访问的状态变量,类型为address。address类型的值大小为160 bits,不支持任何算术操作。适
用于存储合约的地址或其他人的公私钥。public关键字会自动为其修饰的状态变量生成访问函数。没有public关键字的变量将无法被其他合约访问。另
外只有本合约内的代码才能写入。自动生成的函数如下:
function minter() returns (address) {
return minter;
}
当然我们自己增加一个这样的访问函数是行不通的。编译器会报错,指出这个函数与一个状态变量重名。
下一行代码 mapping (address => uint) public balances; 创建了一个public的状态变量,但是其类型更加的复杂。该类型将一些address映射到无
符号整数。mapping可以被认为是一个哈希表,每一个可能的key对应的value被虚拟的初始化为全0.这个类比不是很严谨,对于一个mapping,无法获
取一个包含其所有key或者value的链表。所以我们得自己记着添加了哪些东西到mapping中。更好的方式是维护一个这样的链表,或者使用其他更高级
的数据类型。或者只在不受这个缺陷影响的场景中使用mapping,就像这个例子。在这个例子中由public关键字生成的访问函数将会更加复杂,其代码
大致如下:
function balances(address _account) returns (uint balance) {
return balances[_account];
}
我们可以很方便的通过这个函数查询某个特定账号的余额。
event Sent(address from, address to, uint value); 这行代码声明了一个“事件”。由send函数的最后一行代码触发。客户端(服务端应用也适用)
可以以很低的开销来监听这些由区块链触发的事件。事件触发时,监听者会同时接收到from,to,value这些参数值,可以方便的用于跟踪交易。为了
监听这个事件,你可以使用如下代码:
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount +
" coins were sent from " + result.args.from +
" to " + result.args.to + ".");
console.log("Balances now:\n" +
"Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to));
}
}
注意在客户端中是如何调用自动生成的 balances 函数的。
这里有个比较特殊的函数 Coin。它是一个构造函数,会在合约创建的时候运行,之后就无法被调用。它会永久得存储合约创建者的地址。msg(以及tx
和block)是一个神奇的全局变量,它包含了一些可以被合约代码访问的属于区块链的属性。msg.sender 总是存放着当前函数的外部调用者的地址。
最后,真正被用户或者其他合约调用,用来完成本合约功能的函数是mint和send。如果合约创建者之外的其他人调用mint,什么都不会发生。而send
可以被任何人(拥有一定数量的代币)调用,发送一些币给其他人。注意,当你通过该合约发送一些代币到某个地址,在区块链浏览器中查询该地址
将什么也看不到。因为发送代币导致的余额变化只存储在该代币合约的数据存储中。通过事件我们可以很容易创建一个可以追踪你的新币交易和余额
的“区块链浏览器”。
区块链基础
区块链基础
对于程序员来说,区块链这个概念其实不难理解。因为最难懂的一些东西(挖矿,哈希,椭圆曲线加密,点对点网络等等)只是为了提供一系列的特
性和保障。你只需要接受这些既有的特性,不需要关心其底层的技术。就像你如果仅仅是为了使用亚马逊的AWS,并不需要了解其内部工作原理。
交易交易/事务事务
区块链是一个全局共享的,事务性的数据库。这意味着参与这个网络的每一个人都可以读取其中的记录。如果你想修改这个数据库中的东西,就必须
创建一个事务,并得到其他所有人的确认。事务这个词意味着你要做的修改(假如你想同时修改两个值)只能被完完全全的实施或者一点都没有进
行。
此外,当你的事务被应用到这个数据库的时候,其他事务不能修改该数据库。
举个例子,想象一张表,里面列出了某个电子货币所有账号的余额。当从一个账户到另外一个账户的转账请求发生时,这个数据库的事务特性确保从
一个账户中减掉的金额会被加到另一个账户上。如果因为某种原因,往目标账户上增加金额无法进行,那么源账户的金额也不会发生任何变化。
此外,一个事务会被发送者(创建者)进行密码学签名。这项措施非常直观的为数据库的特定修改增加了访问保护。在电子货币的例子中,一个简单
的检查就可以确保只有持有账户密钥的人,才能从该账户向外转账。
区块区块
区块链要解决的一个主要难题,在比特币中被称为“双花攻击”。当网络上出现了两笔交易,都要花光一个账户中的钱时,会发生什么?一个冲突?
简单的回答是你不需要关心这个问题。这些交易会被排序并打包成“区块”,然后被所有参与的节点执行和分发。如果两笔交易相互冲突,排序靠后的交
易会被拒绝并剔除出区块。
这些区块按时间排成一个线性序列。这也正是“区块链”这个词的由来。区块以一个相当规律的时间间隔加入到链上。对于以太坊,这个间隔大致是17
秒。
作为“顺序选择机制”(通常称为“挖矿”)的一部分,一段区块链可能会时不时被回滚。但这种情况只会发生在整条链的末端。回滚涉及的区块越多,其
发生的概率越小。所以你的交易可能会被回滚,甚至会被从区块链中删除。但是你等待的越久,这种情况发生的概率就越小。
以太坊虚拟机
以太坊虚拟机
总览总览
以太坊虚拟机(EVM)是以太坊中智能合约的运行环境。它不仅被沙箱封装起来,事实上它被完全隔离,也就是说运行在EVM内部的代码不能接触到
网络、文件系统或者其它进程。甚至智能合约与其它智能合约只有有限的接触。
账户账户
以太坊中有两类账户,它们共用同一个地址空间。外部账户,该类账户被公钥-私钥对控制(人类)。合约账户,该类账户被存储在账户中的代码控
制。
外部账户的地址是由公钥决定的,合约账户的地址是在创建该合约时确定的(这个地址由合约创建者的地址和该地址发出过的交易数量计算得到,地
址发出过的交易数量也被称作"nonce")
合约账户存储了代码,外部账户则没有,除了这点以外,这两类账户对于EVM来说是一样的。
每个账户有一个key-value形式的持久化存储。其中key和value的长度都是256比特,名字叫做storage.
另外,每个账户都有一个以太币余额(单位是“Wei"),该账户余额可以通过向它发送带有以太币的交易来改变。
交易交易
一笔交易是一条消息,从一个账户发送到另一个账户(可能是相同的账户或者零账户,见下文)。交易可以包含二进制数据(payload)和以太币。
如果目标账户包含代码,该代码会执行,payload就是输入数据。
如果目标账户是零账户(账户地址是0),交易将创建一个新合约。正如上文所讲,这个合约地址不是零地址,而是由合约创建者的地址和该地址发出
过的交易数量(被称为nonce)计算得到。创建合约交易的payload被当作EVM字节码执行。执行的输出做为合约代码被永久存储。这意味着,为了创
建一个合约,你不需要向合约发送真正的合约代码,而是发送能够返回真正代码的代码。
Gas
以太坊上的每笔交易都会被收取一定数量的gas,gas的目的是限制执行交易所需的工作量,同时为执行支付费用。当EVM执行交易时,gas将按照特
定规则被逐渐消耗。
gas price(以太币计)是由交易创建者设置的,发送账户需要预付的交易费用 = gas price * gas amount。 如果执行结束还有gas剩余,这些gas将被
返还给发送账户。
无论执行到什么位置,一旦gas被耗尽(比如降为负值),将会触发一个out-of-gas异常。当前调用帧所做的所有状态修改都将被回滚。
存储,主存和栈
存储,主存和栈
每个账户有一块持久化内存区域被称为存储。其形式为key-value,key和value的长度均为256比特。在合约里,不能遍历账户的存储。相对于另外两
种,存储的读操作相对来说开销较大,修改存储更甚。一个合约只能对它自己的存储进行读写。
第二个内存区被称为主存。合约执行每次消息调用时,都有一块新的,被清除过的主存。主存可以以字节粒度寻址,但是读写粒度为32字节(256比
特)。操作主存的开销随着其增长而变大(平方级别)。
EVM不是基于寄存器,而是基于栈的虚拟机。因此所有的计算都在一个被称为栈的区域执行。栈最大有1024个元素,每个元素256比特。对栈的访问
只限于其顶端,方式为:允许拷贝最顶端的16个元素中的一个到栈顶,或者是交换栈顶元素和下面16个元素中的一个。所有其他操作都只能取最顶的
两个(或一个,或更多,取决于具体的操作)元素,并把结果压在栈顶。当然可以把栈上的元素放到存储或者主存中。但是无法只访问栈上指定深度
的那个元素,在那之前必须要把指定深度之上的所有元素都从栈中移除才行。
指令集指令集
EVM的指令集被刻意保持在最小规模,以尽可能避免可能导致共识问题的错误实现。所有的指令都是针对256比特这个基本的数据类型的操作。具备常
用的算术,位,逻辑和比较操作。也可以做到条件和无条件跳转。此外,合约可以访问当前区块的相关属性,比如它的编号和时间戳。
消息调用
消息调用
合约可以通过消息调用的方式来调用其它合约或者发送以太币到非合约账户。消息调用和交易非常类似,它们都有一个源,一个目标,数据负载,以
太币,gas和返回数据。事实上每个交易都可以被认为是一个顶层消息调用,这个消息调用会依次产生更多的消息调用。
一个合约可以决定剩余gas的分配。比如内部消息调用时使用多少gas,或者期望保留多少gas。如果在内部消息调用时发生了out-of-gas异常(或者其
他异常),合约将会得到通知,一个错误码被压在栈上。这种情况只是内部消息调用的gas耗尽。在solidity中,这种情况下发起调用的合约默认会触发
一个人工异常。这个异常会打印出调用栈。就像之前说过的,被调用的合约(发起调用的合约也一样)会拥有崭新的主存并能够访问调用的负载。调
用负载被存储在一个单独的被称为calldata的区域。调用执行结束后,返回数据将被存放在调用方预先分配好的一块内存中。
调用层数被限制为1024,因此对于更加复杂的操作,我们应该使用循环而不是递归。
代码调用和库
代码调用和库
存在一种特殊类型的消息调用,被称为callcode。它跟消息调用几乎完全一样,只是加载自目标地址的代码将在发起调用的合约上下文中运行。
这意味着一个合约可以在运行时从另外一个地址动态加载代码。存储,当前地址和余额都指向发起调用的合约,只有代码是从被调用地址获取的。
这使得Solidity可以实现”库“。可复用的库代码可以应用在一个合约的存储上,可以用来实现复杂的数据结构。
日志日志
在区块层面,可以用一种特殊的可索引的数据结构来存储数据。这个特性被称为日志,Solidity用它来实现事件。合约创建之后就无法访问日志数据,
但是这些数据可以从区块链外高效的访问。因为部分日志数据被存储在布隆过滤器(Bloom filter) 中,我们可以高效并且安全的搜索日志,所以那些没
有下载整个区块链的网络节点(轻客户端)也可以找到这些日志。
创建创建
合约甚至可以通过一个特殊的指令来创建其他合约(不是简单的向零地址发起调用)。创建合约的调用跟普通的消息调用的区别在于,负载数据执行
的结果被当作代码,调用者/创建者在栈上得到新合约的地址。
自毁自毁
只有在某个地址上的合约执行自毁操作时,合约代码才会从区块链上移除。合约地址上剩余的以太币会发送给指定的目标,然后其存储和代码被移
除。
注意,即使一个合约的代码不包含自毁指令,依然可以通过代码调用(callcode)来执行这个操作。