微众银行《Solidity智能合约库》区块链工程师的随身工具箱之初体验

  • 向彪
  • 更新于 2021-04-21 10:54
  • 阅读 4847

个人心得:工具一般都是先收藏,然后记住有哪些功能,然后在应用的时候如果能够用不重复造轮子,就直接拿来用!即快速又不用太担心安全相关的问题。

一、智能合约库简介

作为一门实现了图灵完备的智能合约编程语言,Solidity编程语言的开发、设计、迭代、演化的逻辑完全基于区块链,并在区块链领域具有广泛的影响力和详尽的文档,被众多区块链底层平台所支持,其中就包括FISCO BCOS。

但是,Solidity编程语言也存在若干挑战。首先,受区块链昂贵的资源限制,Solidity舍去了诸多在其他语言中常见的特性,例如高级的语法等。其次,流行的Solidity智能合约库多为公有链所开发,与FISCO BCOS存在兼容性的问题。最后,智能合约编程的安全性要求高,且较难对合约进行升级,一旦存在安全漏洞,后果不堪设想。

为了解决上述问题,WeBankBlockchain-SmartDev-Contract智能合约库应运而生,包含了基础类型、数据结构、通用功能、上层业务等智能合约库。用户可根据实际需求进行参考、复用。智能合约库的设计初衷是提供场景化、通用化、可插拔的智能合约代码,从而最大程度地节约开发智能合约的时间,改变智能合约工具库匮乏的局面。

SmartDev-Contract智能合约库是一个“麻雀虽小,五脏俱全”的智能合约的工具类库,通过Solidity的library封装,旨在帮助Solidity开发者提升开发体验,避免重复造轮子,让编写Solidity语言也可以如编写Python语言那样“丝滑顺畅”。

SmartDev-Contract智能合约库的每个合约文件都来自于微众银行区块链工程师的细致打磨,来自于实际使用场景的“聚沙成塔”,覆盖了业务场景开发中的各种“犄角旮旯”,是开发智能合约的“10倍工程师”的不二法门。

从功能上来看,SmartDev-Contract智能合约库涵盖了从基础类型到上层业务的常见代码,用户可根据实际需要进行参考、复用。具体如下:

图片

二、痛点及解决方式

以在Solidity语言中将address类型转为string为例。

过去: 打开搜索引擎或github->搜索关键字“Solidity address convert to string”->找到相关的搜索结果->拷贝相关的代码->粘贴到自己的智能合约代码中。 如果无法搜索到相关的代码,开发者必须重新造轮子,耗时耗力的同时,还可能引入新的风险。

现在: 直接下载智能合约代码库->解压->找到相关的库合约->引入代码->调用相关函数。智能合约代码库地址: https://github.com/WeBankBlockchain/SmartDev-Contract/archive/refs/tags/V1.0.0.zip

pragma solidity ^0.4.25;
import "./LibAddress.sol" 
contract Demo {
    ...
    address addr = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9;
    bytes memory bs = LibAddress.addressToBytes(addr);
}

在这里插入图片描述 测试代码:

pragma solidity ^0.4.24;

import "./LibAddress.sol";

contract Test {

 function get() public view returns (bytes memory) {
   address addr = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9;
     bytes memory  bs = LibAddress.addressToBytes(addr);
        return bs;
    }
}

在这里插入图片描述

开发者引入或拷贝网络上未知来源的代码可能出现重大的bug。同样的,自己重新编写的代码可能因为缺乏测试或实践检验,更易出现风险。智能合约库提供了方便、成熟、安全、低成本的解决方案。

痛点一:计算可能溢出

在智能合约的开发中,数值计算问题不可避免。但是,Solidity内置的运算机制不够安全,因计算问题导致的智能合约安全事故屡见不鲜。

SmartDev-Contract智能合约库提供了安全计算的代码类库。以uint256数据类型为例,LibSafeMathForUint256Utils提供了Uint256类型的相关计算操作,且保证数据的正确性和安全性,包括加法、减法、乘法、除法、取模、乘方、最大值、最小值和平均数等操作。其他的数值类型可以自行参考实现。

LibSafeMathForUint256Utils下载地址: https://gitee.com/WeBankBlockchain/SmartDev-Contract/blob/master/contracts/base_type/LibSafeMathForUint256Utils.sol 下载之后导入需要使用的sol文件中,然后就可以调用LibSafeMathForUint256Utils相关的方法,具体的如下所示:

1、加减乘除运算

function f() public view {
    uint256 a = 25;
    uint256 b = 20;
    // a + b
    uint256 c = LibSafeMathForUint256Utils.add(a,b);
    // a - b
    uint256 d = LibSafeMathForUint256Utils.sub(a,b);
    // a * b
    uint256 e = LibSafeMathForUint256Utils.mul(a,b);
    // a/b
    uint256 f = LibSafeMathForUint256Utils.div(a,b);
}

2、取模运算、乘方运算

function f() public view {
    uint256 a = 25;
    uint256 b = 20;
    // a % b
    uint256 c = LibSafeMathForUint256Utils.mod(a,b);
    // a ^ b
    uint256 d = LibSafeMathForUint256Utils.power(a,b);
}

3、最大值、最小值、平均数运算

function f() public view {
    uint256 a = 25;
    uint256 b = 20;
    // max(a, b)
    uint256 c= LibSafeMathForUint256Utils.max(a,b);
    // min(a, b)
    uint256 d = LibSafeMathForUint256Utils.min(a,b);
    // average(a, b)
    uint256 e = LibSafeMathForUint256Utils.average(a,b);
}

痛点二:转换不够便捷

数值转换工具

基础数据类型转换是编程语言的刚需。LibConverter提供各类Solidity数据基本类型的转换,开发者可以根据此工具扩展为其他的数值转换类型和函数。

LibConverter下载地址: https://gitee.com/WeBankBlockchain/SmartDev-Contract/blob/master/contracts/base_type/LibConverter.sol 下载之后导入需要使用的sol文件中,然后就可以调用LibConverter相关的方法,具体的如下所示:

1、数值类型向下转换,例如uint256转换为uint8。

function f() public view{
    uint256 a = 25;
    uint8 b = LibConverter.toUint8(a);
}

2、数值类型转bytes

function f() public view{
    uint256 a = 25;
    bytes memory b = LibConverter.uintToBytes(a);
}

3、bytes转数值类型

function f() public view{
    bytes memory a = "25";
    int b = LibConverter.bytesToInt(a);
}

address转换工具

address类型是Solidity特有的数据类型之一。在日常的程序运行逻辑中,常常会涉及到address与bytes和string类型的互转。LibAddress实现了上述的转换功能。 LibAddress下载地址: https://gitee.com/WeBankBlockchain/SmartDev-Contract/blob/master/contracts/base_type/LibAddress.sol 下载之后导入需要使用的sol文件中,然后就可以调用LibAddress相关的方法,具体的如下所示:

address转bytes

address addr = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9;
bytes memory bs = LibAddress.addressToBytes(addr);

bytes转address

bytes memory bs = newbytes(20);
address addr = LibAddress.bytesToAddress(bs);

address转string

address addr = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9;
string memory addrStr = LibAddress.addressToString(addr);

string转address

string memory str="0xE0f5206BBD039e7b0592d8918820024e2a7437b9";
address addr = LibAddress.stringToAddress(str);

痛点三:数组操作不够丰富

在Solidity中原生支持的数组类型,不支持排序、查找、比较、移除、添加、翻转、合并、去重等众多常用的功能。

SmartDev-Contract智能合约库基于动态数组的结构封装了“LibArrayForUint256Utils”的常用工具函数实现。开发者也可根据自身需要的数据结构,自行封装相关的工具类。 LibArrayForUint256Utils下载地址: https://gitee.com/WeBankBlockchain/SmartDev-Contract/blob/master/contracts/base_type/LibArrayForUint256Utils.sol 下载之后导入需要使用的sol文件中,然后就可以调用LibArrayForUint256Utils相关的方法,具体的如下所示:

1、添加不重复的元素

uint[] private array;
function f() public view {
    array=new uint[](0);
    // array add element 2
    LibArrayForUint256Utils.addValue(array,2);
    // array: {2}
}

2、合并两个数组

uint[] private array1;
uint[] private array1;
function f() public view {
    array1=new uint[](2);
    array2=new uint[](2);
    LibArrayForUint256Utils.extend(array1,array2);
    // array1 length 4
}

3、对数组去重

uint[] private array;
function f() public view {
    array=new uint[](2);
    array[0]=2;
    array[1]=2;
    LibArrayForUint256Utils.distinct(array);
    // array: {2}
}

4、对数组升序排序

uint[] private array;
function f() public view {
    array=new uint[](3);
    array[0]=3;
    array[1]=2;
    array[2]=1;
    LibArrayForUint256Utils.qsort(array);  
    // array: {1,2,3}
}

5、二分查找

基于已排序的数组,支持二分查找,提升查找的效率。

uint[] private array;
function f() public view {
    array=new uint[](3);
    array[0]=3;
    array[1]=2;
    array[2]=1;
    uint256 key=3;
    LibArrayForUint256Utils.binarySearch(array,key);  
    // array: {true, 1}
}

6、删除元素

uint[] private array;
function f() public view {
    array=new uint[](3);
    array[0]=3;
    array[1]=2;
    array[2]=1;
    LibArrayForUint256Utils.removeByValue(array,2);  
    // array: {3, 1}
}

痛点四:不提供字符串内置操作

对字符串的操作是开发中较为常见的操作,例如获取字符串长度、大小写转换等。在其他开发语言中,通常会提供一些内置的字符串处理类库。但Solidity本身没有提供字符串内置操作,因此,这部分需求可通过使用SmartDev-Contract智能合约库来满足。

在SmartDev-Contract智能合约库中,对于字符串,我们提供了丰富的功能,这里列举三个比较常见的函数。 LibString下载地址: https://gitee.com/WeBankBlockchain/SmartDev-Contract/blob/master/contracts/base_type/LibString.sol 下载之后导入需要使用的sol文件中,然后就可以调用LibString相关的方法,具体的如下所示:

1、获取字符串长度

下面的示例中,分别示意了获取字符串长度、字符串字节数:

pragma solidity ^0.4.25;
import "./LibString.sol";
contract Test {
    function f() public{
        string memory str = "你好";
        uint256 lenOfChars = LibString.lenOfChars(str);
        uint256 lenOfBytes = LibString.lenOfBytes(str);
        require(lenOfChars == 2);
        require(lenOfBytes == 6);
    }
}

2、大小写转换

下面示例中,将大写转换为小写:

pragma solidity ^0.4.25;
import "./LibString.sol";
contract Test {
    function f() public view returns(string memory)  {
        string memory c = LibString.toUppercase("abcd");// Expected to be ABCD
        return c;
    }
}

3、相等比较

pragma solidity ^0.4.25;
import "./LibString.sol";
contract Test {
    function f() public view {
        bool r = LibString.equal("abcd","abcd");//Expected to be true
        require(r);
    }
}

4、字符串前缀比较

pragma solidity ^0.4.25;
import "./LibString.sol";
contract Test {
    function f() public view {
        bool r = LibString.startWith("abcd","ab");//Expected to be true
        require(r);
    }
}

痛点五:高级数据结构不完备

作为一门面向区块链的语言,Solidity为了节省资源,在数据结构层面砍掉了许多特性,这使得和常规语言相比,其在使用上存在较大差异。一方面,Solidity内部仅提供了数组、mapping等数据结构,如果存在其他需求,需自助实现;另一方面,对于mapping,其内部的键仅保存了哈希值,无法获取键的原值。

综上所述,我们在SmartDev-Contract智能合约库中提供了对数据结构的增强支持,以资参考、使用。

Mapping映射

在下面这个例子中,定义了一个Map,然后向里面存放了三个键值对。再通过迭代的方式将key取出,存放在事件里。

https://gitee.com/WeBankBlockchain/SmartDev-Contract/blob/master/contracts/data_structure/LibBytesMap.sol

pragma solidity ^0.4.25;
import "./LibBytesMap.sol";

contract Test {
    using LibBytesMap for LibBytesMap.Map;
    LibBytesMap.Map private map;
    event Log(bytes key, uint256 index);
    function f() public {
        string memory k1 = "k1";
        string memory k2 = "k2";
        string memory k3 = "k3";
        string memory v1 = "v1";
        string memory v2 = "v2";
        string memory v3 = "v3";
        map.put(bytes(k1),bytes(v1));
        map.put(bytes(k2),bytes(v2));
        map.put(bytes(k3),bytes(v3));
        // 开始迭代
        uint256 i = map.iterate_start();
        while(map.can_iterate(i)){
            emit Log(map.getKeyByIndex(i), i);
            i = map.iterate_next(i);
        }
    }
}

address set集合

作为多数高级编程语言标配的数据结构,set是一种集合的数据结构,其中每个独特属性的元素都是唯一的。

SmartDev-Contract智能合约库依托动态数组和mapping,实现了一个基础的set集合。此外,由于Solidity不支持泛型机制,开发者可以参考此工具,实现其他元素的set集合。

https://gitee.com/WeBankBlockchain/SmartDev-Contract/blob/master/contracts/data_structure/LibAddressSet.sol

pragma solidity ^0.4.25;

import "./LibAddressSet.sol";

contract Test {
    using LibAddressSet for LibAddressSet.AddressSet;
    LibAddressSet.AddressSet private addressSet;
    event Log(uint256 size);
    function testAddress() public {
        //添加元素;
        addressSet.add(address(1));
        // {1}
        // 查询set容器数量
        uint256 size = addressSet.getSize();
        require(size == 1);
        // 获取指定index的元素
        address addr = addressSet.get(0);
        require(addr == address(1));
        // 返回set中所有的元素
        addressSet.getAll();
        // {0x1}
        // 判断元素是否存在
        bool contains = addressSet.contains(address(1));
        require(contains== true);
        // 删除元素
        addressSet.remove(address(1));
    }
}

除了上述的几个工具,开源库中还有很多值得学习和应用的工具,有兴趣的小伙伴可以自行研究,如下图所示: 在这里插入图片描述

总结

个人心得:工具一般都是先收藏,然后记住有哪些功能,然后在应用的时候如果能够用不重复造轮子,就直接拿来用!即快速又不用太担心安全相关的问题。

本文内容参考: https://mp.weixin.qq.com/s/TibfdZohpbppqz4ZuBItaA

GitHub代码库地址: https://github.com/WeBankBlockchain/SmartDev-Contract

gitee代码库地址: https://gitee.com/WeBankBlockchain/SmartDev-Contract

点赞 4
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
向彪
向彪
0x8822...2ae6
一个来自于神秘湘西的程序猿,专注于区块链的应用落地研究,共享、共识、共建、共赢!~