Solidity学习

/*

* 我也是在学习阶段,通过用自己

* 的理解表述一遍,来加深理解,

* 如果有错误之处欢迎探讨~

*/

基于ERC20的token合约:

源码地址:https://ethereum.org/token

pragmasolidity ^0.4.16;

因为合约代码会在以太坊节点上的以太坊虚拟机EVM上被运行,而solidity发展更新很快,像constant以前还能修饰函数,现在没有了,被pure和view这两个关键字代替了 (详见:https://github.com/ethereum/solidity/issues/992)。所以上面这句告诉EVM后续代码里面有些新功能是0.4.16版本新加的(详见:https://www.reddit.com/r/ethereum/comments/6vxbht/solidity_version_0416_released/)。

contractowned {

address publicowner;

functionowned()public{

owner = msg.sender;

}

modifieronlyOwner {

require(msg.sender == owner);

_;

}

functiontransferOwnership(address

newOwner) onlyOwnerpublic{

owner = newOwner;

}

}

上面这段代码给这个合约设置了一些权限。contract类似于类(详见:http://solidity.readthedocs.io/en/develop/contracts.html)。

它首先申明了一个public的地址变量owner,用来保存一个地址变量,地址变量有160位,一般用[0x40个16进制数],如:0x06012c8cf97bead5deae237070f9587f8e7a266d 这是以太坊上一个游戏CryptoKitties的合约地址。说到这里,在以太坊中,地址有两种,一种是账户地址,一种是合约地址:

图一:账户地址

图二 合约地址

主要看图中画了红圈的地方。图二可以从https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d查到,而且如果合约部署者选择上传合约源码,你是可以看到的。

然后有个构造函数owned(),solidity里面构造函数是可选可不选的,但是就是写了也只允许写一个,所以不支持重载(overloading)。

When a contract is created, its constructor (a function with the same name as the contract) is executed once. A constructor is optional. Only one constructor is allowed, and this means overloading is not supported. (详见:http://solidity.readthedocs.io/en/develop/contracts.html)

owned()里面把msg.sender的值保存在owner变量里面。msg.sender是指调用/使用合约的地址。比如如果你用账户A的地址去把这个合约部署到了以太坊网络上,msg.sender就是A的地址,通过后续的函数对owner的授权(比如有的函数只能owner执行),A的地址就成了这个合约的拥有者,所以变量名为owner也是很合理。

接下来是一个modifier function。

Modifiers can be used to easily change the behaviour of functions. For example, they can automatically check a condition prior to executing the function. Modifiers are inheritable properties of contracts and may be overridden by derived contracts. (详见:http://solidity.readthedocs.io/en/develop/contracts.html#function-modifiers)

下面姑且称modifier类型的为函数吧。

在这里,有个onlyOwner函数,里面require(msg.sender == owner);在第一次合约部署时owner已经设定好了,除非后续合约拥有者调用了transferOwnership(),否则owner就是那个合约部署者的地址。现在这里要求msg.sender和owner相等,也就是只有合约拥有者才满足条件。_; 表示如果require(msg.sender == owner); 满足,就接着执行后面的。

然后有个transferOwnership函数,它接受一个address类型的参数newOwner,然后它调用了onlyOwner这个modifier函数,表示执行这个transferOwnership() 函数必须要进入到onlyOwner()这个modifier里面去判断是不是onlyOwner()的要求,这里就是要求msg.sender == owner,所以只有合约拥有者owner才能执行transferOwnership()这个函数。

interfacetokenRecipient {function

receiveApproval(address_from,

uint256_value,address_token,

bytes_extraData)public;}

这里有个interface结构

Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions:

Cannot inherit other contracts or interfaces.

Cannot define constructor.

Cannot define variables.

Cannot define structs.

Cannot define enums.

Some of these restrictions might be lifted in the future.

(详见http://solidity.readthedocs.io/en/develop/contracts.html#interfaces)

interface类似一个抽象类,现阶段里面不能有其他的contract类和interface抽象类,不能有构造函数(Java的可以有构造函数,但是不允许实例化),不能定义变量,结构体,枚举类型。

这里定义了tokenRecipient这个interface。查看后面的代码,只有这里才用到了tokenRecipient。这里我也不是很懂,欢迎大神探讨。

/**

* Set allowance for other address

* and notify

*

* Allows `_spender` to spend no more

* than `_value` tokens in your behalf,

* and then ping the contract about it

*

* @param _spender The address authorized to spend

* @param _value the max amount they can spend

* @param _extraData some extra information

* to send to the approved contract

*/

functionapproveAndCall(address_spender,

uint256_value,bytes_extraData)public

returns(boolsuccess) {

tokenRecipientspender =

tokenRecipient(_spender);

if(approve(_spender, _value)) {

spender.receiveApproval(

msg.sender, _value, this, _extraData);

return true;

}

}

tokenRecipientis an interface that is used to make calls to other contract that have implementation ofreceiveApprovalfunction.

Its used to make call to external contract where spender is address of that contract.

详见:https://ethereum.stackexchange.com/questions/36763/help-me-understand-this-sample-code-for-new-erc20-token

(直接复制粘贴代码,我预览时发现代码经常超出边框而无法显示。)

接下来,进入TokenERC20 contract。

申明了string类型的两个变量name,symbol,用来保存token的名字,和token的符号。还有decimals,一个uint8类型的变量,表示你的这个token可以支持小数点后最多多少位。官方文档强烈建议是18位,不清楚为啥,反正eth是1 eth = 10^18 Wei。为了以后你的token与eth互换时更加方便?由于decimals是uint8类型,所以最大为256,就是你的token最多只能支持小数点后256位。solidity里面uint类型有uint8到uint256,按照每8位增长。

/ : Signed and unsigned integers of various sizes. Keywords to in steps of (unsigned of 8 up to 256 bits) and to . and are aliases for and , respectively.

(详见:http://solidity.readthedocs.io/en/develop/types.html#value-types)

还申明了totalSupply,这是一个uint256类型。

上面这些变量都被public修饰。关于public,external,internal,private的说明:

Since Solidity knows two kinds of function calls (internal ones that do not create an actual EVM call (also called a “message call”) and external ones that do), there are four types of visibilities for functions and state variables.

Functions can be specified as being , , or , where the default is. For state variables, is not possible and the default is .

:

External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function cannot be called internally (i.e. does not work, but works). External functions are sometimes more efficient when they receive large arrays of data.

:

Public functions are part of the contract interface and can be either called internally or via messages. For public state variables, an automatic getter function (see below) is generated.

:

Those functions and state variables can only be accessed internally (i.e. from within the current contract or contracts deriving from it), without using .

:

Private functions and state variables are only visible for the contract they are defined in and not in derived contracts.

(详见:http://solidity.readthedocs.io/en/develop/contracts.html#visibility-and-getters)

英语渣表示上面看了半天又是翻译软件又是原文对照还是不知道他说了一个啥。还是看它的例子吧:

在contract C里面,f()是一个private函数,所以只能在contract C里面被执行,即便是继承了contract C的contract E,也无法使用。

而compute() 是一个internal函数,可以被contract E里面直接用,而且不必写c.compute(3,5),直接是compute(3,5)。但是不可以被其他contract,像contract D使用。

setData()是public函数,所有的contract都可以使用,但是要先实例化,用c.setData(3)来实现。

最后,有个要注意的,虽然data是contract C里面的private变量,但是通过contract C里面的函数的public修饰,data是可以被任意合约修改和读取的。如果data代表的是某些资产,这就会是一个漏洞,毕竟以太坊出现了很多因为solidity代码有漏洞而导致巨大损失的。

接下来,涉及一个新的结构,mapping,类似映射?反正和数组用法差不多。

The next line, also creates a public state variable, but it is a more complex datatype. The type maps addresses to unsigned integers. Mappings can be seen as hash tables which are virtually initialized such that every possible key exists and is mapped to a value whose byte-representation is all zeros. This analogy does not go too far, though, as it is neither possible to obtain a list of all keys of a mapping, nor a list of all values. So either keep in mind (or better, keep a list or use a more advanced data type) what you added to the mapping or use it in a context where this is not needed, like this one. The getter function created by the keyword is a bit more complex in this case.

(详见:http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html)

第一个balanceOf[address1] = xxx,其中xxx表示address1里面我们这个token的数量。

第二个allowance[address1][address2] = xxx, 其中xxx表示address1允许address2使用(转走)数量为xxx的这个token。为啥要有这么怪异的功能?后面代码会用到,而且这样可以让其他合约能够使用你的token,更好地和其他合约交互。

接着是两个event,event是个新概念。

The linedeclares a so-called “event” which is emitted in the last line of the function. User interfaces (as well as server applications of course) can listen for those events being emitted on the blockchain without much cost. As soon as it is emitted, the listener will also receive the arguments,and, which makes it easy to track transactions.

(详见:http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html)

event就是给全网节点广播一个事件。尤其是允许轻钱包能够根据变化有效地做出反应。以太坊钱包客户端有轻钱包和全节点钱包两种。全节点钱包像ethereum官方推荐的Ethereum-Wallet,启动这个客户端,在使用它之前他得把整个以太坊的区块链都下载下来。然后根据区块链里面储存的交易数据能够得出某个地址有多少余额。轻钱包比如metamask,这是一个浏览器插件,支持Chrome和Firefox浏览器。它上面余额的变化需要查询全节点。所以有了event,就只需在轻钱包上实现listen,就能尽快得知状态的改变了。

所以这里有个Transfer event,有个Burn event,为了和函数区别,一般申明event时,开始那个字母大写。这个Transfer会在真的有转账发生的时候被实例化执行。Burn是在有人销毁部分数量的token时会被实例化执行,后面代码里面都有。

接着是contract TokenERC20的构造函数,通过传入三个参数initialSupply, tokenName, tokenSymbol,把前面申明的totalSupply,name,symbol初始化了。还记得以前申明decimals是用的uint8吧?这里要和uint256类型的变量initialSupply进行计算,所以需要uint256()这个转换函数。还有token的总供应量是传入参数乘上10^18(这里我们decimals设置的是18)个最小单位。然后把所以的token通过balanceOf数组保存到了这个合约部署者地址下。

接着是一个internal函数_transfer(),前面说了,被internal修饰的函数只能被这个合约内部调用,代码里面的注释也说了。而且是以_开头。如果变量也以_开头,应该表示这个变量只能在这个函数里面使用吧(没确定)。_transfer有三个参数,来的地址_from,到账的地址_to,转账的数量_value。里面有几个条件限制,用require()实现。

第一个是require(_to != 0x0); 意思你不能转给0x0000000000000000000000000000000000000000(别数了,40个0)这个地址。这个地址一看,里面有7228+eth,足足500+W刀(详见:https://etherscan.io/address/0x0000000000000000000000000000000000000000)但是地址没有转出记录。也就是只进不出。原因应该是0x0这个地址拥有者他弄丢了对应的private key,没有权限使用里面的eth了。所以有阵子合约里面没有burn()这个token总量部分销毁函数时,coder就会把多余的token转到0x0这个地址,让那部分token锁死在这个地址上。现在有了burn()函数,这种做法就是不允许了。更多关于0x0这个地址的趣闻,可以参考https://zhuanlan.zhihu.com/p/34363341。

第二个require(balanceOf[_from] >= _value);这个很好理解,你不能转出超过你的余额的token。

第三个require(balanceOf[_to] + _value > balanceOf[_to]);这个是为了检测是否溢出。两个超大正数加起来,如果超过了uint256能表示的正整数范围,就会有可能产生溢出,导致结果为负数。

然后有个uint类型的变量

previousBalances = balanceOf[_from] + balanceOf[_to];

这是为了后续assert()进行检查。

然后就是对balanceOf[_from]和balanceOf[_to]进行操作,即_from的余额减去_value,_to的余额加上_value。这里就可以看到,在以太坊里面,token等资产就是一个账本而已,具体是一个地址到uint256的映射。转账等都是在这个映射上修改相应的数值即可。

接下来:Transfer(_from, _to, _value);是调用了Transfer()这个Event,向全网广播,让light client知道转账发生了。

然后,assert(balanceOf[_from] + balanceOf[_to] == previousBalances);根据注释,这个是一个做代码静态分析的,我理解是必须保证assert()里的的bool值为true,所以在这里就是确保了转账前和转账后两个账户的余额之和是相等的。

之前实现的那个_transfer()是internal函数无法被合约之外调用,如果以后要和其他合约交互的话就行不通。所以这里定义了一个transfer()函数,而且是public的。这个函数实现了把_value数量的token从msg.sender发送给_to地址。即把自己的token发出去。而且直接调用了_transfer()函数。而且_transfer()已经检查了那些非法情况。所以这里就实现的很简洁。

这个是transferFrom()函数,对比transfer()函数,它多传入一个参数,表示msg.sender把_from的token转给_to。所以这里需要一个require(),去检查allowance[_from][msg.sender]的值,这个值表示的是_from地址允许msg.sender使用_from地址里面的token的数量。所以_value要小于等于这个值。而且在转账之前需要把allowance[_from][msg.sender]这个值减掉_value,因为接下来马上就要把_value数量的token在msg.sender的授权下从_from转给_to,所以需要减掉对应msg.sender 能授权的token的数量,即allowance[_from][msg.sender]值。

transFrom()函数也是一个public函数,都可以调用。然后有个bool类型的返回值success。如果前面所有步骤都完全正确执行,就会返回true。

这个是approve()函数,它是msg.sender授权给_spender数量为_value的token去任意使用。因为任何地址都可能拥有这个合约所发行的token,然后设置别可以用他的token,所以任何合约都可以调用,这个函数就需要设置为public。但是大家是否发现这个函数里面并没有对msg.sender是否有_value数量的token进行检查,所以msg.sender是可以给出空头支票的。。。就是msg.sender可以在账户里没有这个token时仍然可以用这个函数进行授权。因为_spender真的要使用这笔token的话,最后还是落在了要用_transfer()函数去转账,把token转出来。而_transfer()函数里面做了这方面的检查。

这个和approve类似,多了一个

and then ping the contract about it

就是联系合约告诉它这件事?

这个是销毁部分token,注意这个函数是public,所以任何地址都可以调用这个函数销毁自己账户上的这个不超过余额的token。然后这个token的总供应量totalSupply也会对应的减_value数量的token。然后也会掉Burn进行全网广播。

这个和burn相似,不过是直接销毁_from地址里的授权给msg.sender的token。然后也会减少总的token数totalSupply以及调用Burn进行全网广播。

到此为止基于ERC20的token把所有的必须的函数等都实现了。还有一些高级功能可以自去看,像什么设置你的token和eth买卖的价格,设置账户冻结功能等,都是有其实际用处的。

正是这些代码,让发布虚拟货币有了标准化的流程,去年大火的ICO就是通过发布虚拟货币,让别人用真金实银来提前认购。结合https://ethereum.org/token,利用Ethereum-Wallet钱包,可以快速部署这些代码到链上,实现15min就能ICO,那么你就会发布虚拟货币,而且你还理解背后的原理啦。

但是现在大火的是dapp,就是用solidity来开发区块链游戏。明天找一个听说交易量过了2.5个亿的项目的solidity源码来分析^_^

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180313G1RL8G00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动