智能合约开发 | Solidity依然是对开发者最友好的语言,不过有些开发技巧有点反常识

in #blockchain6 years ago (edited)

Solidity是一种类似于JavaScript的语言,用来编写区块链智能合约。目前来看,Solidity依然是全球最受欢迎的智能合约开发语言,它支持静态类型,继承和库等特性,加上周围开发生态的支持,如truffle和web3,让开发和部署一个DApp简单了很多。一个有经验的JavaScript开发者可以很快上手Solidity开发,但如果要用好Solidity就必须要了解Solidity和区块链的特性,而有些特性有点”反常识“,下面让我们来一起学习一下。

计算很贵

这点是开发dapp的最需要牢记的一点了,因为区块链是全网共识,也就是说每次修改一个状态一个变量都需要全网的节点达成一致,这是很昂贵的操作,所以当我们要修改变量的状态时,先想想这一点。

慎用循环

想象这样一个游戏场景:一个游戏中有3个队伍,每个用户可以投入一定的eth加入任意一个队伍,游戏结束时拥有最多用户的队伍将获胜,根据用户投入的多少瓜分10eth的奖金。

开发者会遇到的问题是,游戏结束的时候如何给用户结算用户赢得了多少eth?

你可能会说我可以这样做:

  1. 定义三个数组,对应三个队伍,然后用数组记录参与此队伍的用户地址
  2. 游戏结束的时候找到数组长度(length)最长的一个数组,对应的这个队伍就是获胜的队伍
  3. 然后再用for循环遍历这个数组,将用户赢得的eth算出来,写入到用户的合约账户中
  4. 用户可以随时提币

第三点中涉及到了一个for循环和写入操作,如果有10个用户的话,相当于循环10次修改合约状态10次。如果这个量级增加到几千的话,那就有点可怕了,因为这整个操作消耗的gas可能会超过一个区块的gas limit,从 https://etherscan.io/blocks 这个网站我们可以看到最近的几个区块的gas limit大概是8,000,000 gas/block,如果超过这个数字则你的写入操作都会失败。从用户角度来说,获胜的用户永远无法得到自己应得的eth,结果用户骂娘走人,游戏死掉。

2018-11-13 at 10.25 PM.png

所以记住,慎用循环,最好不要使用未知循环次数的循环

mapping and iteration

mappingsolidity中非常常用的一种数据类型, 是一种类似于 K->V hashmap的结构。不但可以定义一个mapping,甚至可以定义mapping of mapping以及mapping of mapping of mapping的多重嵌套,不过就本码农看来,定义1-3级mapping(mapping出现4次)的场景比较多,超过4级(mapping出现4次)的就不是很多了。

mapping (address => Player) public mapAddrToPlayer; // 1级
mapping (uint => mapping (address => mapping (uint => mapping (uint => uint)))) public mapPlayerEthByTree;  // 4级

虽然mapping很常用,可以却不能遍历mapping的所有key,拿mapAddrToPlayer这个结构来说,key是用户的地址,如果我想遍历所有的地址,拿到用户信息,是做不到的。如果想遍历,需要单独维护一个数组来存储所有用户的地址(key)。

now其实不是now

我们一般写代码时,获得的now就是当时的时间,而在solidity中获得的now却不是真正的now,而是区块链上最新一个区块的时间。

过程是这样的,我在当前时间向一个合约发送了一次查询,想要得到当前的now,得到这次请求的节点会去查询最近一个区块被打包的时间,把这个时间返回给我,这个值就是我想要的now了。如果这个区块是在10秒前被打包成功的,那我得到的值是会比真实世界的now少10秒的。

所以now不可靠,在使用的时候一定要注意着一点。

不支持浮点数

solidity中不支持浮点数,所以处理浮点数的时候要通过整数来表示。另外做除法的时候结果也不会出现浮点数,除法1/2结果是0,除法5/2结果是2

因为uint类型有溢出的问题,所以在处理数学的加减乘乘方以及根号的运算时,我们通常用safemath来处理。

foo.add(bar)    // 加法
foo.mul(bar)    // 乘法
foo.sub(bar)    // 减法

你在开发过程中遇到了哪些坑?欢迎留言。