fisco bcosV3 Table智能合约开发

环境 : fisco bcos 3.11.0

webase-front : 3.1.1

console 3.8.0

前言

最近在做毕设,数据的存储方式考虑使用fisco-bcos的table表存储,经过这几天的研究,发现对于fisco2fisco3版本的table表合约功能差异还是比较大的,比较起来V3的table合约功能性更丰富,更加的方便开发。

读者们要是没用过v3的链子,可以在fisco3 这里简单启动一条链子,Air版本的搭建和fisco2搭建的链子命令无多大差别【文章后面也有对应的控制台搭建命令,注意控制台2和3版本的链子不互通

然后就是webase-front要使用3.0以上的版本,链接在这里https://webasedoc.readthedocs.io/zh-cn/lab/docs/WeBASE-Install/developer.html

关于fisco3 的Table合约

不知道是不是webase-front 版本的问题,我并未在其代码仓库里找到Table.sol合约的文件,只有KVTable.sol合约。然后我去github的fisco仓库找到了fisco3的版本合约文件,还附带两个合约,都需要import进去才行

[更新一下,这些合约也可以在控制台3.8.0上的contracts/solidity文件夹上找到,注意:v3版本有两个Table合约,一个是3.2.0版本以上,一个是3.2.0以前的版本,我所有介绍的是3.2.0以上的,官方文档给的是3.2.0以前的例子]

Table.sol

js 复制代码
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.6.10 <0.8.20;
pragma experimental ABIEncoderV2;
import "./EntryWrapper.sol";

// KeyOrder指定Key的排序规则,字典序和数字序,如果指定为数字序,key只能为数字
enum KeyOrder {Lexicographic, Numerical}
struct TableInfo {
    KeyOrder keyOrder;
    string keyColumn;
    string[] valueColumns;
}

// 更新字段,用于update
struct UpdateField {
    string columnName;
    // 考虑工具类
    string value;
}

// 筛选条件,大于、大于等于、小于、小于等于
enum ConditionOP {GT, GE, LT, LE, EQ, NE, STARTS_WITH, ENDS_WITH, CONTAINS}
struct Condition {
    ConditionOP op;
    string field;
    string value;
}

// 数量限制
struct Limit {
    uint32 offset;
    // count limit max is 500
    uint32 count;
}

// 表管理合约,是静态Precompiled,有固定的合约地址
abstract contract TableManager {
    // 创建表,传入TableInfo
    function createTable(string memory path, TableInfo memory tableInfo) public virtual returns (int32);

    // 创建KV表,传入key和value字段名
    function createKVTable(string memory tableName, string memory keyField, string memory valueField) public virtual returns (int32);

    // 只提供给Solidity合约调用时使用
    function openTable(string memory path) public view virtual returns (address);

    // 变更表字段
    // 只能新增字段,不能删除字段,新增的字段默认值为空,不能与原有字段重复
    function appendColumns(string memory path, string[] memory newColumns) public virtual returns (int32);

    // 获取表信息
    function descWithKeyOrder(string memory tableName) public view virtual returns (TableInfo memory);
}

// 表合约,是动态Precompiled,TableManager创建时指定地址
abstract contract Table {
    // 按key查询entry
    function select(string memory key) public virtual view returns (Entry memory);

    // 按条件批量查询entry,condition为空则查询所有记录
    function select(Condition[] memory conditions, Limit memory limit) public virtual view returns (Entry[] memory);

    // 按照条件查询count数据
    function count(Condition[] memory conditions) public virtual view returns (uint32);

    // 插入数据
    function insert(Entry memory entry) public virtual returns (int32);

    // 按key更新entry
    function update(string memory key, UpdateField[] memory updateFields) public virtual returns (int32);

    // 按条件批量更新entry,condition为空则更新所有记录
    function update(Condition[] memory conditions, Limit memory limit, UpdateField[] memory updateFields) public virtual returns (int32);

    // 按key删除entry
    function remove(string memory key) public virtual returns (int32);
    // 按条件批量删除entry,condition为空则删除所有记录
    function remove(Condition[] memory conditions, Limit memory limit) public virtual returns (int32);
}

abstract contract KVTable {
    function get(string memory key) public view virtual returns (bool, string memory);

    function set(string memory key, string memory value) public virtual returns (int32);
}

EntryWrapper.sol

这个类似于mybatisplus里面的wrapper条件构造器,用来进行附加查询条件使用,还有Entry用来做返回数据的数据结构

js 复制代码
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.6.10 <0.8.20;
pragma experimental ABIEncoderV2;
import "./Cast.sol";

// 记录,用于select和insert
struct Entry {
    string key;
    string[] fields; // 考虑2.0的Entry接口,临时Precompiled的问题,考虑加工具类接口
}

contract EntryWrapper {   
    Cast constant cast =  Cast(address(0x100f));  
    Entry entry;
    constructor(Entry memory _entry) public {
        entry = _entry;
    }
    function setEntry(Entry memory _entry) public {
        entry = _entry;
    }
    function getEntry() public view returns(Entry memory) {
        return entry;
    }
    function fieldSize() public view returns (uint256) {
        return entry.fields.length;
    }
    function getInt(uint256 idx) public view returns (int256) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        return cast.stringToS256(entry.fields[idx]);
    }
    function getUInt(uint256 idx) public view returns (uint256) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        return cast.stringToU256(entry.fields[idx]);
    }
    function getAddress(uint256 idx) public view returns (address) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        return cast.stringToAddr(entry.fields[idx]);
    }
    function getBytes64(uint256 idx) public view returns (bytes1[64] memory) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        return bytesToBytes64(bytes(entry.fields[idx]));
    }
    function getBytes32(uint256 idx) public view returns (bytes32) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        return cast.stringToBytes32(entry.fields[idx]);
    }
    function getString(uint256 idx) public view returns (string memory) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        return entry.fields[idx];
    }
    function set(uint256 idx, int256 value) public returns(int32) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        entry.fields[idx] = cast.s256ToString(value);
        return 0;
    }
    function set(uint256 idx, uint256 value) public returns(int32) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        entry.fields[idx] = cast.u256ToString(value);
        return 0;
    }
    function set(uint256 idx, string memory value) public returns(int32) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        entry.fields[idx] = value;
        return 0;
    }
    function set(uint256 idx, address value) public returns(int32) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        entry.fields[idx] = cast.addrToString(value);
        return 0;
    }
    function set(uint256 idx, bytes32 value) public returns(int32) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        entry.fields[idx] = cast.bytes32ToString(value);
        return 0;
    }
    function set(uint256 idx, bytes1[64] memory value) public returns(int32) {
        require(idx >= 0 && idx < fieldSize(), "Index out of range!");
        entry.fields[idx] = string(bytes64ToBytes(value));
        return 0;
    }
    function setKey(string memory value) public {
        entry.key = value;
    }
    function getKey() public view returns (string memory) {
        return entry.key;
    }
    function bytes64ToBytes(bytes1[64] memory src) private pure returns(bytes memory) {
        bytes memory dst = new bytes(64);
        for(uint32 i = 0; i < 64; i++) {
            dst[i] = src[i][0];
        }
        return dst;
    }
    function bytesToBytes64(bytes memory src) private pure returns(bytes1[64] memory) {
        bytes1[64] memory dst;
        for(uint32 i = 0; i < 64; i++) {
            dst[i] = src[i];
        }
        return dst;
    }
}

Cast.sol

js 复制代码
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.6.10 <0.8.20;
pragma experimental ABIEncoderV2;

abstract contract Cast {
    function stringToS256(string memory) public virtual view returns (int256);
    function stringToS64(string memory) public virtual view returns (int64);
    function stringToU256(string memory) public virtual view returns (uint256);
    function stringToAddr(string memory) public virtual view returns (address);
    function stringToBytes32(string memory) public virtual view returns (bytes32);
    
    function s256ToString(int256) public virtual view returns (string memory);
    function s64ToString(int64) public virtual view returns (string memory);
    function u256ToString(uint256) public virtual view returns (string memory);
    function addrToString(address) public virtual view returns (string memory);
    function bytes32ToString(bytes32) public virtual view returns (string memory);
}

编写TableTest

这里我直接用官网的例子代码进行测试,不过官网例子与实际的table合约有出入,需要进行修改

https://fisco-bcos-doc.readthedocs.io/zh-cn/latest/docs/contract_develop/c++_contract/use_crud_precompiled.html

js 复制代码
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.6.10 <0.8.20;
pragma experimental ABIEncoderV2;
import "./Table.sol";


contract TestTable{

// 创建TableManager对象,其在区块链上的固定地址是0x1002
TableManager constant tm =  TableManager(address(0x1002));
Table table;
string constant TABLE_NAME = "t_test";
constructor () public{
    // 创建t_test表,表的主键名为id,其他字段名为name和age
     string[] memory columnNames = new string[](2);
     columnNames[0] = "name";
     columnNames[1] = "age";
      KeyOrder keyOrder;
      
     TableInfo memory tf = TableInfo(KeyOrder.Numerical,"id", columnNames);
    tm.createTable(TABLE_NAME, tf);
    
    // 获取真实的地址,存在合约中
    address t_address = tm.openTable(TABLE_NAME);
    require(t_address!=address(0x0),"");
    table = Table(t_address);
} 

function insert(string memory id,string memory name,string memory age) public returns (int32){
    string[] memory columns = new string[](2);
    columns[0] = name;
    columns[1] = age;
    Entry memory entry = Entry(id, columns);
    int32 result = table.insert(entry);
    // emit InsertResult(result);
    return result;
}

function update(string memory id, string memory name, string memory age) public returns (int32){
    UpdateField[] memory updateFields = new UpdateField[](2);
    updateFields[0] = UpdateField("name", name);
    updateFields[1] = UpdateField("age", age);

    int32 result = table.update(id, updateFields);

    return result;
}

function remove(string memory id) public returns(int32){
    int32 result = table.remove(id);
   
    return result;
}


function select(string memory id) public view returns (string memory,string memory)
{
    Entry memory entry = table.select(id);

    string memory name;
    string memory age;
    if(entry.fields.length==2){
        name = entry.fields[0];
        age = entry.fields[1];
    }
    return (name,age);
}


// enum ConditionOP {GT, GE, LT, LE, EQ, NE, STARTS_WITH, ENDS_WITH, CONTAINS}
// struct Condition {
//     ConditionOP op;
//     string field;
//     string value;
// }
function selectMore(string memory age)
    public
    view
    returns (Entry[] memory entries)
{
    Condition[] memory conds = new Condition[](1);
    Condition memory eq= Condition({op: ConditionOP.EQ, field: "age",value: age});
    conds[0] = eq;
    Limit memory limit = Limit({offset: 0, count: 100});
    entries = table.select(conds, limit);
    return entries;
}



}

TableInfo的问题

实际的TableInfo 结构如下

js 复制代码
// KeyOrder指定Key的排序规则,字典序和数字序,如果指定为数字序,key只能为数字
enum KeyOrder {Lexicographic, Numerical}
struct TableInfo {
    KeyOrder keyOrder;
    string keyColumn;
    string[] valueColumns;
}

是需要三个参数的,而官网的例子只用两个参数,缺少的第一个参数是枚举类,意思是你的主键是string类型还是数字类型,需要标明。

Condition的问题

实际的Condition结构如下

js 复制代码
// 筛选条件,大于、大于等于、小于、小于等于
enum ConditionOP {GT, GE, LT, LE, EQ, NE, STARTS_WITH, ENDS_WITH, CONTAINS}
struct Condition {
    ConditionOP op;
    string field;
    string value;
}

官网的只有两个参数,缺少的那个参数是field,也就是此条件是要用在表的哪个字段时安个。

关于主键的问题

在fisco2的table表合约开发时候,因其的设计,导致主键是可以重复的。

但在测试fisco3的table表合约开发的时候,我用重复的主键添加多个数据,发现它遵守了主键唯一的规则,有兴趣的读者可以去测试一下,特别是用TestTable的selectMore,把field改成主键字段,可以测试到返回不了多个数据

结语

这段时间会继续开发v3的table合约,在开发的时候遇到的坑和新发现会持续更新到博客上

相关推荐
OpenBuild.xyz3 小时前
我是如何从 0 到 1 找到 Web3 工作的?
人工智能·web3·去中心化·区块链·智能合约
Sui_Network3 小时前
Sui 如何支持各种类型的 Web3 游戏
大数据·数据库·人工智能·游戏·web3·区块链
区块链蓝海14 小时前
Secured Finance携手Axelar及Squid提升流动性,迎接USDFC主网
区块链
yunteng5211 天前
solidity之Foundry安装配置(一)
web3·区块链·solidity·foundry
qiquandongkh1 天前
2025年股指期货和股指期权合约交割的通知!
大数据·金融·区块链
yoona10202 天前
Rust编程语言入门教程 (七)函数与控制流
开发语言·rust·区块链·学习方法
MetaverseMan3 天前
从sumsub获取用户图片
区块链
电报号dapp1194 天前
区块链虚拟币资产去中心化私钥钱包开发
人工智能·去中心化·区块链·智能合约
漠缠4 天前
股票与比特币投资困境分析及解决方案
人工智能·区块链