fisco bcos CRUD编写合约的注意点
环境: ubuntu20
fisco 2.8.0
solidity 0.4.25
webase-front 1.5.2
前言
当前fiscobcos已经更新到V3版本,对于crub式的合约也支持了更多接口,有兴趣的可以尝试一下,今天写的是V2版本的fiscobcos,所以Solidity版本选择了0.4.25.
pragma solidity ^0.4.25;contract TableFactory {function openTable(string) public view returns (Table); //open tablefunction createTable(string, string, string) public returns (int256); //create table
}//select condition
contract Condition {function EQ(string, int256) public view;function EQ(string, string) public view;function EQ(string, address) public view;function NE(string, int256) public view;function NE(string, string) public view;function GT(string, int256) public view;function GE(string, int256) public view;function LT(string, int256) public view;function LE(string, int256) public view;function limit(int256) public view;function limit(int256, int256) public view;
}//one record
contract Entry {function getInt(string) public view returns (int256);function getUInt(string) public view returns (uint256);function getAddress(string) public view returns (address);function getBytes64(string) public view returns (bytes1[64]);function getBytes32(string) public view returns (bytes32);function getString(string) public view returns (string);function set(string, int256) public;function set(string, uint256) public;function set(string, string) public;function set(string, address) public;
}//record sets
contract Entries {function get(int256) public view returns (Entry);function size() public view returns (int256);
}//Table main contract
contract Table {function select(string, Condition) public view returns (Entries);function insert(string, Entry) public returns (int256);function update(string, Entry, Condition) public returns (int256);function remove(string, Condition) public returns (int256);function newEntry() public view returns (Entry);function newCondition() public view returns (Condition);
}contract KVTableFactory {function openTable(string) public view returns (KVTable);function createTable(string, string, string) public returns (int256);
}//KVTable per permiary key has only one Entry
contract KVTable {function get(string) public view returns (bool, Entry);function set(string, Entry) public returns (int256);function newEntry() public view returns (Entry);
}
编写一个管理员合约来管理
通过编写Ownable合约
pragma solidity ^0.4.25;//管理员合约
contract Ownable {address public owner;event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// 构造器constructor () public {owner = msg.sender;}// 管理员操作修饰器modifier onlyOwner() {require(msg.sender == owner);_;}
// 管理员权限转移function transferOwnership(address newOwner) public onlyOwner {require(newOwner != address(0));emit OwnershipTransferred(owner, newOwner);owner = newOwner;}}
然后写CRUD合约的使用可以,继承此管理员合约,在写函数方法的时候可以添加修饰器的onlyOwner来约束使用函数权限。
简单的例子
Test的crud合约
pragma solidity ^0.4.25;
import "./Table.sol";
import "./Ownable.sol";// 存储合约
contract Test is Ownable{TableFactory tf;event Result(int count);//每次影响数据的事件声明//状态 (1是正常)int256 constant status = 1;int256 constant badStatus = -1;string constant TABLE_NAME = "test1";constructor() public {tf = TableFactory(0x1001);tf.createTable(TABLE_NAME, "account", "status1,status2");}function add(address account ) public returns(int) {int count = int(0);Table table = tf.openTable(TABLE_NAME);if(account != address(0) && !_isExist(table,account)){Entry entry = table.newEntry();entry.set("status1", status);entry.set("status2",status);count = table.insert(addressToString(account), entry);}emit Result(count);return count;}//删除function remove(address account) public onlyOwner returns(int) {int count = int(0);Table table = tf.openTable(TABLE_NAME); if(_isExist(table,account)){Condition condition = table.newCondition();count = table.remove(addressToString(account),condition); }emit Result(count);return count; }// 查找function check(address account) public view returns(int256[]){Table table = tf.openTable(TABLE_NAME);Condition condition = table.newCondition();condition.EQ("status1", status);Entries entries = table.select(addressToString(account), condition);int256[] memory status1_id_list = new int256[](uint256(entries.size()));for (int256 i = 0; i < entries.size(); ++i) {Entry entry = entries.get(i);status1_id_list[uint256(i)] = entry.getInt("status1");}return status1_id_list;}// 更新function update(address account,int256 money) public onlyOwner returns(int) {int count = int(0);Table table = tf.openTable(TABLE_NAME); if(_isExist(table,account)){Condition condition = table.newCondition();Entry entry = table.newEntry();entry.set("status1",badStatus);count = table.update(addressToString(account),entry,condition); }emit Result(count);return count; }function _isExist(Table _table, address account) internal view returns(bool) {Condition condition = _table.newCondition();condition.EQ("status1", status);return _table.select(addressToString(account), condition).size() > int(0);}// 判断用户是否存在function isExist( address account) public view returns(bool) {Table table = tf.openTable(TABLE_NAME);return _isExist(table,account) ;}// 地址转换成Stringfunction addressToString(address _address) public pure returns (string memory _uintAsString) {uint _i = uint256(_address);if (_i == 0) {return "0";}uint j = _i;uint len;while (j != 0) {len++;j /= 10;}bytes memory bstr = new bytes(len);uint k = len - 1;while (_i != 0) {bstr[k--] = byte(uint8(48 + _i % 10));_i /= 10;}return string(bstr);}}
接下来总结一下会踩到的坑
部署表问题
在CRUD合约中,如果你部署了一个表名叫test1
,那么这条fisco链子就会产生表,并且其他合约也能调用,只要正确打开Table。但是如果你写好了一个合约,在构造函数里面 有部署表的代码
TableFactory tf;string constant TABLE_NAME = "test1";constructor() public {tf = TableFactory(0x1001);tf.createTable(TABLE_NAME, "account", "status1,status2");}
那么,你再次完善合约的代码时候,需要重新部署此合约,那么 tf.createTable(TABLE_NAME, "account", "status1,status2");
此段代码就不需要了,因为CRUD合约是数据和代码是分离的,那么重新部署合约后,表的数据还会在,这就是与传统开发合约的区别所在。
表字段创建的问题
tf.createTable(TABLE_NAME, "account", "status1,status2");
这个方法一定是需要输入三个入参的,第一个是表名,第二个是主键的,第三个是其他表字段【字段之间要用英文逗号隔开】,所以如果你设计的表需要只主键这一个字段的话,那么CRUD合约就不适合你,推荐用数组或者mapping
来解决业务。
主键的数据类型选择问题
可以看一下table合约对主键的定义
function createTable(string, string, string) public returns (int256); //create table
function insert(string, Entry) public returns (int256);
说明主键是要用字符串存储的,比方说我想用adddress类型的作为主键,那么就需要用转换方法,将其进行转换成字符串
function addressToString(address _address) public pure returns (string memory _uintAsString) {uint _i = uint256(_address);if (_i == 0) {return "0";}uint j = _i;uint len;while (j != 0) {len++;j /= 10;}bytes memory bstr = new bytes(len);uint k = len - 1;while (_i != 0) {bstr[k--] = byte(uint8(48 + _i % 10));_i /= 10;}return string(bstr);}}
控制台使用查表的问题
控制台的使用sql语句可以查看系统里表的结构和数据
查看表结构
[group:1]> desc test1
[{"key_field":"account","value_field":"status1,status2"}
]
也可以查询语句,注意v2的链子必须要带主键才能查询
【但是这个主键与传统的mysql的主键又有不同,因为此主键允许重复的】
[group:1]> select * from test1 where account = 261409877424322348048187072124586908683860685589
{account=261409877424322348048187072124586908683860685589, status1=1, status2=1}
1 row in set.
这是的account 是进过addressToString方法转换成的。
Entry
查询select的时候会返回Entries
,里面可能有多个Entry,所以要用for循环进行调用get方法
contract Entries {function get(int256) public view returns (Entry);function size() public view returns (int256);
}
Condition
查询select还需要用到Condition这个条件类,EQ是相等,NE是不相等,GT是大于,GE是大于等于,LT是小于,LE是小于等于 ,要注意的是函数第一参数是放字段名,然后可以匹配的数据类型是int256,string和address。limit就是展示多少数据,展示哪写数据。
function EQ(string, int256) public view;function EQ(string, string) public view;function EQ(string, address) public view;function NE(string, int256) public view;function NE(string, string) public view;function GT(string, int256) public view;function GE(string, int256) public view;function LT(string, int256) public view;function LE(string, int256) public view;function limit(int256) public view;function limit(int256, int256) public view;