智能合约开发 | Solidity依然是对开发者最友好的语言,不过有些开发技巧有点反常识
Solidity是一种类似于JavaScript的语言,用来编写区块链智能合约。目前来看,Solidity依然是全球最受欢迎的智能合约开发语言,它支持静态类型,继承和库等特性,加上周围开发生态的支持,如truffle和web3,让开发和部署一个DApp简单了很多。一个有经验的JavaScript开发者可以很快上手Solidity开发,但如果要用好Solidity就必须要了解Solidity和区块链的特性,而有些特性有点”反常识“,下面让我们来一起学习一下。
计算很贵
这点是开发dapp的最需要牢记的一点了,因为区块链是全网共识,也就是说每次修改一个状态一个变量都需要全网的节点达成一致,这是很昂贵的操作,所以当我们要修改变量的状态时,先想想这一点。
慎用循环
想象这样一个游戏场景:一个游戏中有3个队伍,每个用户可以投入一定的eth加入任意一个队伍,游戏结束时拥有最多用户的队伍将获胜,根据用户投入的多少瓜分10eth的奖金。
开发者会遇到的问题是,游戏结束的时候如何给用户结算用户赢得了多少eth?
你可能会说我可以这样做:
- 定义三个数组,对应三个队伍,然后用数组记录参与此队伍的用户地址
- 游戏结束的时候找到数组长度(length)最长的一个数组,对应的这个队伍就是获胜的队伍
- 然后再用for循环遍历这个数组,将用户赢得的eth算出来,写入到用户的合约账户中
- 用户可以随时提币
第三点中涉及到了一个for循环和写入操作,如果有10个用户的话,相当于循环10次修改合约状态10次。如果这个量级增加到几千的话,那就有点可怕了,因为这整个操作消耗的gas可能会超过一个区块的gas limit,从 https://etherscan.io/blocks 这个网站我们可以看到最近的几个区块的gas limit大概是8,000,000 gas/block,如果超过这个数字则你的写入操作都会失败。从用户角度来说,获胜的用户永远无法得到自己应得的eth,结果用户骂娘走人,游戏死掉。
所以记住,慎用循环,最好不要使用未知循环次数的循环。
mapping and iteration
mapping
是solidity
中非常常用的一种数据类型, 是一种类似于 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) // 减法
你在开发过程中遇到了哪些坑?欢迎留言。