智能合约开发

FISCO BCOS平台目前支持Solidity及Precompiled两类合约形式。

  • Solidity合约与以太坊相同,用Solidity语法实现,最高支持0.5.2版本。
  • KVTable合约的读写接口与Table合约的CRUD接口通过在Solidity合约中支持分布式存储预编译合约,可以实现将Solidity合约中数据存储在FISCO BCOS平台AMDB的表结构中,实现合约逻辑与数据的分离。
  • 预编译(Precompiled)合约使用C++开发,内置于FISCO BCOS平台,相比于Solidity合约具有更好的性能,其合约接口需要在编译时预先确定,适用于逻辑固定但需要共识的场景,例如群组配置。关于预编译合约的开发将在下一节进行介绍。

Solidity合约开发

使用KVTable合约读写接口

注解

  • 为实现AMDB创建的表可被多个合约共享访问,其表名是群组内全局可见且唯一的,所以无法在同一条链上的同一个群组中,创建多个表名相同的表
  • KVTable功能在2.3.0版本添加,2.3.0以上版本的链可以使用此功能。

KVTable合约实现键值型读写数据的方式,KVTable合约接口声明如下:

  1. pragma solidity ^0.4.24;
  2. contract KVTableFactory {
  3. function openTable(string) public view returns (KVTable);
  4. // 创建KVTable,参数分别是表名、主键列名、以逗号分割的字段名,字段可以有多个
  5. function createTable(string, string, string) public returns (int256);
  6. }
  7. //一条记录
  8. contract Entry {
  9. function getInt(string) public constant returns (int256);
  10. function getUInt(string) public constant returns (int256);
  11. function getAddress(string) public constant returns (address);
  12. function getBytes64(string) public constant returns (bytes1[64]);
  13. function getBytes32(string) public constant returns (bytes32);
  14. function getString(string) public constant returns (string);
  15. function set(string, int256) public;
  16. function set(string, uint256) public;
  17. function set(string, string) public;
  18. function set(string, address) public;
  19. }
  20. //KVTable 每个键对应一条entry
  21. contract KVTable {
  22. function get(string) public view returns (bool, Entry);
  23. function set(string, Entry) public returns (int256);
  24. function newEntry() public view returns (Entry);
  25. }

提供一个合约案例KVTableTest.sol,代码如下:

  1. pragma solidity ^0.4.24;
  2. import "./Table.sol";
  3. contract KVTableTest {
  4. event SetResult(int256 count);
  5. KVTableFactory tableFactory;
  6. string constant TABLE_NAME = "t_kvtest";
  7. constructor() public {
  8. //The fixed address is 0x1010 for KVTableFactory
  9. tableFactory = KVTableFactory(0x1010);
  10. tableFactory.createTable(TABLE_NAME, "id", "item_price,item_name");
  11. }
  12. //get record
  13. function get(string id) public view returns (bool, int256, string) {
  14. KVTable table = tableFactory.openTable(TABLE_NAME);
  15. bool ok = false;
  16. Entry entry;
  17. (ok, entry) = table.get(id);
  18. int256 item_price;
  19. string memory item_name;
  20. if (ok) {
  21. item_price = entry.getInt("item_price");
  22. item_name = entry.getString("item_name");
  23. }
  24. return (ok, item_price, item_name);
  25. }
  26. //set record
  27. function set(string id, int256 item_price, string item_name)
  28. public
  29. returns (int256)
  30. {
  31. KVTable table = tableFactory.openTable(TABLE_NAME);
  32. Entry entry = table.newEntry();
  33. // the length of entry's field value should < 16MB
  34. entry.set("id", id);
  35. entry.set("item_price", item_price);
  36. entry.set("item_name", item_name);
  37. // the first parameter length of set should <= 255B
  38. int256 count = table.set(id, entry);
  39. emit SetResult(count);
  40. return count;
  41. }
  42. }

KVTableTest.sol调用了KVTable合约,实现的是创建用户表t_kvtest,并对t_kvtest表进行读写的功能。t_kvtest表结构如下,该表记录某公司仓库中物资,以唯一的物资编号作为主key,保存物资的名称和价格。

id*item_nameitem_price
100010001001Laptop6000

重要

客户端需要调用转换为Java文件的合约代码,需要将KVTableTest.sol和Table.sol放入控制台的contracts/solidity目录下,通过控制台的编译脚本sol2java.sh生成SImpleTableTest.java。

使用Table合约CRUD接口

访问 AMDB 需要使用Table合约CRUD接口,Table合约声明于Table.sol,该接口是数据库合约,可以创建表,并对表进行增删改查操作。

注解

为实现AMDB创建的表可被多个合约共享访问,其表名是群组内全局可见且唯一的,所以无法在同一条链上的同一个群组中,创建多个表名相同的表。 Table的CRUD接口一个key下可以有多条记录,使用时会进行数据批量操作,包括批量写入和范围查询。对应此特性,推荐使用关系型数据库MySQL作为后端数据库。 使用KVTtable的get/set接口时,推荐使用RocksDB作为后端数据库,因RocksDB是Key-Value存储的非关系型数据库,使用KVTable接口时单key操作效率更高。

Table.sol文件代码如下:

  1. pragma solidity ^0.4.24;
  2. contract TableFactory {
  3. function openTable(string) public constant returns (Table); // 打开表
  4. function createTable(string,string,string) public returns(int); // 创建表
  5. }
  6. // 查询条件
  7. contract Condition {
  8. //等于
  9. function EQ(string, int) public;
  10. function EQ(string, string) public;
  11. //不等于
  12. function NE(string, int) public;
  13. function NE(string, string) public;
  14. //大于
  15. function GT(string, int) public;
  16. //大于或等于
  17. function GE(string, int) public;
  18. //小于
  19. function LT(string, int) public;
  20. //小于或等于
  21. function LE(string, int) public;
  22. //限制返回记录条数
  23. function limit(int) public;
  24. function limit(int, int) public;
  25. }
  26. // 单条数据记录
  27. contract Entry {
  28. function getInt(string) public constant returns(int);
  29. function getAddress(string) public constant returns(address);
  30. function getBytes64(string) public constant returns(byte[64]);
  31. function getBytes32(string) public constant returns(bytes32);
  32. function getString(string) public constant returns(string);
  33. function set(string, int) public;
  34. function set(string, string) public;
  35. function set(string, address) public;
  36. }
  37. // 数据记录集
  38. contract Entries {
  39. function get(int) public constant returns(Entry);
  40. function size() public constant returns(int);
  41. }
  42. // Table主类
  43. contract Table {
  44. // 查询接口
  45. function select(string, Condition) public constant returns(Entries);
  46. // 插入接口
  47. function insert(string, Entry) public returns(int);
  48. // 更新接口
  49. function update(string, Entry, Condition) public returns(int);
  50. // 删除接口
  51. function remove(string, Condition) public returns(int);
  52. function newEntry() public constant returns(Entry);
  53. function newCondition() public constant returns(Condition);
  54. }

注解

  • Table合约的insert、remove、update和select函数中key的类型为string,其长度最大支持255字符。
  • Entry的get/set接口的key的类型为string,其长度最大支持255字符,value支持的类型有int256(int)、address和string,其中string的不能超过16MB。

提供一个合约案例TableTest.sol,代码如下:

  1. pragma solidity ^0.4.24;
  2. import "./Table.sol";
  3. contract TableTest {
  4. event CreateResult(int count);
  5. event InsertResult(int count);
  6. event UpdateResult(int count);
  7. event RemoveResult(int count);
  8. // 创建表
  9. function create() public returns(int){
  10. TableFactory tf = TableFactory(0x1001); // TableFactory的地址固定为0x1001
  11. // 创建t_test表,表的key_field为name,value_field为item_id,item_name
  12. // key_field表示AMDB主key value_field表示表中的列,可以有多列,以逗号分隔
  13. int count = tf.createTable("t_test", "name", "item_id,item_name");
  14. emit CreateResult(count);
  15. return count;
  16. }
  17. // 查询数据
  18. function select(string name) public constant returns(bytes32[], int[], bytes32[]){
  19. TableFactory tf = TableFactory(0x1001);
  20. Table table = tf.openTable("t_test");
  21. // 条件为空表示不筛选 也可以根据需要使用条件筛选
  22. Condition condition = table.newCondition();
  23. Entries entries = table.select(name, condition);
  24. bytes32[] memory user_name_bytes_list = new bytes32[](uint256(entries.size()));
  25. int[] memory item_id_list = new int[](uint256(entries.size()));
  26. bytes32[] memory item_name_bytes_list = new bytes32[](uint256(entries.size()));
  27. for(int i=0; i<entries.size(); ++i) {
  28. Entry entry = entries.get(i);
  29. user_name_bytes_list[uint256(i)] = entry.getBytes32("name");
  30. item_id_list[uint256(i)] = entry.getInt("item_id");
  31. item_name_bytes_list[uint256(i)] = entry.getBytes32("item_name");
  32. }
  33. return (user_name_bytes_list, item_id_list, item_name_bytes_list);
  34. }
  35. // 插入数据
  36. function insert(string name, int item_id, string item_name) public returns(int) {
  37. TableFactory tf = TableFactory(0x1001);
  38. Table table = tf.openTable("t_test");
  39. Entry entry = table.newEntry();
  40. entry.set("name", name);
  41. entry.set("item_id", item_id);
  42. entry.set("item_name", item_name);
  43. int count = table.insert(name, entry);
  44. emit InsertResult(count);
  45. return count;
  46. }
  47. // 更新数据
  48. function update(string name, int item_id, string item_name) public returns(int) {
  49. TableFactory tf = TableFactory(0x1001);
  50. Table table = tf.openTable("t_test");
  51. Entry entry = table.newEntry();
  52. entry.set("item_name", item_name);
  53. Condition condition = table.newCondition();
  54. condition.EQ("name", name);
  55. condition.EQ("item_id", item_id);
  56. int count = table.update(name, entry, condition);
  57. emit UpdateResult(count);
  58. return count;
  59. }
  60. // 删除数据
  61. function remove(string name, int item_id) public returns(int){
  62. TableFactory tf = TableFactory(0x1001);
  63. Table table = tf.openTable("t_test");
  64. Condition condition = table.newCondition();
  65. condition.EQ("name", name);
  66. condition.EQ("item_id", item_id);
  67. int count = table.remove(name, condition);
  68. emit RemoveResult(count);
  69. return count;
  70. }
  71. }

TableTest.sol调用了 AMDB 专用的智能合约Table.sol,实现的是创建用户表t_test,并对t_test表进行增删改查的功能。t_test表结构如下,该表记录某公司员工领用物资和编号。

name*item_nameitem_id
BobLaptop100010001001

重要

客户端需要调用转换为Java文件的合约代码,需要将TableTest.sol和Table.sol放入控制台的contracts/solidity目录下,通过控制台的编译脚本sol2java.sh生成TableTest.java。

预编译合约开发

一. 简介

预编译(precompiled)合约是一项以太坊原生支持的功能:在底层使用c++代码实现特定功能的合约,提供给EVM模块调用。FISCO BCOS继承并且拓展了这种特性,在此基础上发展了一套功能强大并易于拓展的框架precompiled设计原理。 本文作为一篇入门指导,旨在指引用户如何实现自己的precompiled合约,并实现precompiled合约的调用。

二. 实现预编译合约

2.1 流程

实现预编译合约的流程:

  • 分配合约地址

调用solidity合约或者预编译合约需要根据合约地址来区分,地址空间划分:

地址用途地址范围
以太坊precompiled0x0001-0x0008
保留0x0008-0x0fff
FISCO BCOS precompied0x1000-0x1006
FISCO BCOS预留0x1007-0x5000
用户分配区间0x5001 - 0xffff
CRUD预留0x10000+
solidity其他

用户分配地址空间为0x5001-0xffff,用户需要为新添加的预编译合约分配一个未使用的地址,预编译合约地址必须唯一, 不可冲突

FISCO BCOS中实现的precompild合约列表以及地址分配:

地址功能源码(libprecompiled目录)
0x1000系统参数管理SystemConfigPrecompiled.cpp
0x1001表工厂合约TableFactoryPrecompiled.cpp
0x1002CRUD操作实现CRUDPrecompiled.cpp
0x1003共识节点管理ConsensusPrecompiled.cpp
0x1004CNS功能CNSPrecompiled.cpp
0x1005存储表权限管理AuthorityPrecompiled.cpp
0x1006并行合约配置ParallelConfigPrecompiled.cpp
  • 定义合约接口

同solidity合约,设计合约时需要首先确定合约的ABI接口, precomipiled合约的ABI接口规则与solidity完全相同,solidity ABI链接

定义预编译合约接口时,通常需要定义一个有相同接口的solidity合约,并且将所有的接口的函数体置空,这个合约我们称为预编译合约的接口合约,接口合约在调用预编译合约时需要使用。

  1. pragma solidity ^0.4.24;
  2. contract Contract_Name {
  3. function interface0(parameters ... ) {}
  4. ....
  5. function interfaceN(parameters ... ) {}
  6. }
  • 设计存储结构

预编译合约涉及存储操作时,需要确定存储的表信息(表名与表结构,存储数据在FISCO BCOS中会统一抽象为表结构), 存储结构

注解

不涉及存储操作可以省略该流程。

  • 实现调用逻辑

实现新增合约的调用逻辑,需要新实现一个c++类,该类需要继承Precompiled, 重载call函数, 在call函数中实现各个接口的调用行为。

  1. // libprecompiled/Precompiled.h
  2. class Precompiled
  3. {
  4. virtual bytes call(std::shared_ptr<ExecutiveContext> _context, bytesConstRef _param,
  5. Address const& _origin = Address()) = 0;
  6. };

call函数有三个参数:

std::shared_ptr<ExecutiveContext> _context : 保存交易执行的上下文

bytesConstRef _param : 调用合约的参数信息,本次调用对应合约接口以及接口的参数可以从_param解析获取

Address const& _origin : 交易发送者,用来进行权限控制

如何实现一个Precompiled类在下面的sample中会详细说明。

  • 注册合约

最后需要将合约的地址与对应的类注册到合约的执行上下文,这样通过地址调用precompiled合约时合约的执行逻辑才能被正确识别执行, 查看注册的预编译合约列表。 注册路径:

  1. file libblockverifier/ExecutiveContextFactory.cpp
  2. function initExecutiveContext

2.2 示例合约开发

  1. // HelloWorld.sol
  2. pragma solidity ^0.4.24;
  3. contract HelloWorld{
  4. string name;
  5. function HelloWorld(){
  6. name = "Hello, World!";
  7. }
  8. function get()constant returns(string){
  9. return name;
  10. }
  11. function set(string n){
  12. name = n;
  13. }
  14. }

上述源码为solidity编写的HelloWorld合约, 本章节会实现一个相同功能的预编译合约,通过step by step使用户对预编译合约编写有直观的认识。 示例的c++源码路径

  1. libprecompiled/extension/HelloWorldPrecompiled.h
  2. libprecompiled/extension/HelloWorldPrecompiled.cpp
2.2.1 分配合约地址

参照地址分配空间,HelloWorld预编译合约的地址分配为:

  1. 0x5001
2.2.2 定义合约接口

需要实现HelloWorld合约的功能,接口与HelloWorld接口相同, HelloWorldPrecompiled的接口合约:

  1. pragma solidity ^0.4.24;
  2. contract HelloWorldPrecompiled {
  3. function get() public constant returns(string) {}
  4. function set(string _m) {}
  5. }
2.2.3 设计存储结构

HelloWorldPrecompiled需要存储set的字符串值,所以涉及到存储操作,需要设计存储的表结构。

表名: _ext_hello_world_

表结构:

keyvalue
hello_keyhello_value

该表只存储一对键值对,key字段为hello_key,value字段为hello_value 存储对应的字符串值,可以通过set(string)接口修改,通过get()接口获取。

2.2.4 实现调用逻辑

添加HelloWorldPrecompiled类,重载call函数,实现所有接口的调用行为,call函数源码

用户自定义的Precompiled合约需要新增一个类,在类中定义合约的调用行为,在示例中添加HelloWorldPrecompiled类,然后主要需要完成以下工作:

  • 接口注册
  1. // 定义类中所有的接口
  2. const char* const HELLO_WORLD_METHOD_GET = "get()";
  3. const char* const HELLO_WORLD_METHOD_SET = "set(string)";
  4. // 在构造函数进行接口注册
  5. HelloWorldPrecompiled::HelloWorldPrecompiled()
  6. {
  7. // name2Selector是基类Precompiled类中成员,保存接口调用的映射关系
  8. name2Selector[HELLO_WORLD_METHOD_GET] = getFuncSelector(HELLO_WORLD_METHOD_GET);
  9. name2Selector[HELLO_WORLD_METHOD_SET] = getFuncSelector(HELLO_WORLD_METHOD_SET);
  10. }
  • 创建表

定义表名,表的字段结构

  1. // 定义表名
  2. const std::string HELLO_WORLD_TABLE_NAME = "_ext_hello_world_";
  3. // 主键字段
  4. const std::string HELLOWORLD_KEY_FIELD = "key";
  5. // 其他字段字段,多个字段使用逗号分割,比如 "field0,field1,field2"
  6. const std::string HELLOWORLD_VALUE_FIELD = "value";
  1. // call函数中,表存在时打开,否则首先创建表
  2. Table::Ptr table = openTable(_context, HELLO_WORLD_TABLE_NAME);
  3. if (!table)
  4. {
  5. // 表不存在,首先创建
  6. table = createTable(_context, HELLO_WORLD_TABLE_NAME, HELLOWORLD_KEY_FIELD,
  7. HELLOWORLD_VALUE_FIELD, _origin);
  8. if (!table)
  9. {
  10. // 创建表失败,返回错误码
  11. }
  12. }

获取表的操作句柄之后,用户可以实现对表操作的具体逻辑。

  • 区分调用接口

通过getParamFunc解析_param可以区分调用的接口。 注意:合约接口一定要先在构造函数中注册

  1. uint32_t func = getParamFunc(_param);
  2. if (func == name2Selector[HELLO_WORLD_METHOD_GET])
  3. {
  4. // get() 接口调用逻辑
  5. }
  6. else if (func == name2Selector[HELLO_WORLD_METHOD_SET])
  7. {
  8. // set(string) 接口调用逻辑
  9. }
  10. else
  11. {
  12. // 未知接口,调用错误,返回错误码
  13. }
  • 参数解析与结果返回

调用合约时的参数包含在call函数的_param参数中,是按照Solidity ABI格式进行编码,使用dev::eth::ContractABI工具类可以进行参数的解析,同样接口返回时返回值也需要按照该编码格编码。Solidity ABI

dev::eth::ContractABI类中我们需要使用abiIn abiOut两个接口,前者用户参数的序列化,后者可以从序列化的数据中解析参数

  1. // 序列化ABI数据, c++类型数据序列化为evm使用的格式
  2. // _id : 函数接口声明对应的字符串, 一般默认为""即可。
  3. template <class... T> bytes abiIn(std::string _id, T const&... _t)
  4. // 将序列化数据解析为c++类型数据
  5. template <class... T> void abiOut(bytesConstRef _data, T&... _t)

下面的示例代码说明接口如何使用:

  1. // 对于transfer接口 : transfer(string,string,uint256)
  2. // 参数1
  3. std::string str1 = "fromAccount";
  4. // 参数2
  5. std::string str2 = "toAccount";
  6. // 参数3
  7. uint256 transferAmoumt = 11111;
  8. dev::eth::ContractABI abi;
  9. // 序列化, abiIn第一个string参数默认""
  10. bytes out = abi.abiIn("", str1, str2, transferAmoumt);
  11. std::string strOut1;
  12. std::string strOut2;
  13. uint256 amoumt;
  14. // 解析参数
  15. abi.abiOut(out, strOut1, strOut2, amount);
  16. // 解析之后
  17. // strOut1 = "fromAccount";
  18. // strOut2 = "toAccount"
  19. // amoumt = 11111

最后,给出HelloWorldPrecompiled call函数的完整实现源码链接

  1. bytes HelloWorldPrecompiled::call(dev::blockverifier::ExecutiveContext::Ptr _context,
  2. bytesConstRef _param, Address const& _origin)
  3. {
  4. // 解析函数接口
  5. uint32_t func = getParamFunc(_param);
  6. //
  7. bytesConstRef data = getParamData(_param);
  8. bytes out;
  9. dev::eth::ContractABI abi;
  10. // 打开表
  11. Table::Ptr table = openTable(_context, HELLO_WORLD_TABLE_NAME);
  12. if (!table)
  13. {
  14. // 表不存在,首先创建
  15. table = createTable(_context, HELLO_WORLD_TABLE_NAME, HELLOWORLD_KEY_FIELD,
  16. HELLOWORLD_VALUE_FIELD, _origin);
  17. if (!table)
  18. {
  19. // 创建表失败,无权限?
  20. out = abi.abiIn("", CODE_NO_AUTHORIZED);
  21. return out;
  22. }
  23. }
  24. // 区分调用接口,各个接口的具体调用逻辑
  25. if (func == name2Selector[HELLO_WORLD_METHOD_GET])
  26. { // get() 接口调用
  27. // 默认返回值
  28. std::string retValue = "Hello World!";
  29. auto entries = table->select(HELLOWORLD_KEY_FIELD_NAME, table->newCondition());
  30. if (0u != entries->size())
  31. {
  32. auto entry = entries->get(0);
  33. retValue = entry->getField(HELLOWORLD_VALUE_FIELD);
  34. }
  35. out = abi.abiIn("", retValue);
  36. }
  37. else if (func == name2Selector[HELLO_WORLD_METHOD_SET])
  38. { // set(string) 接口调用
  39. std::string strValue;
  40. abi.abiOut(data, strValue);
  41. auto entries = table->select(HELLOWORLD_KEY_FIELD_NAME, table->newCondition());
  42. auto entry = table->newEntry();
  43. entry->setField(HELLOWORLD_KEY_FIELD, HELLOWORLD_KEY_FIELD_NAME);
  44. entry->setField(HELLOWORLD_VALUE_FIELD, strValue);
  45. int count = 0;
  46. if (0u != entries->size())
  47. { // 值存在,更新
  48. count = table->update(HELLOWORLD_KEY_FIELD_NAME, entry, table->newCondition(),
  49. std::make_shared<AccessOptions>(_origin));
  50. }
  51. else
  52. { // 值不存在,插入
  53. count = table->insert(
  54. HELLOWORLD_KEY_FIELD_NAME, entry, std::make_shared<AccessOptions>(_origin));
  55. }
  56. if (count == CODE_NO_AUTHORIZED)
  57. { // 没有表操作权限
  58. PRECOMPILED_LOG(ERROR) << LOG_BADGE("HelloWorldPrecompiled") << LOG_DESC("set")
  59. << LOG_DESC("non-authorized");
  60. }
  61. out = abi.abiIn("", u256(count));
  62. }
  63. else
  64. { // 参数错误,未知的接口调用
  65. PRECOMPILED_LOG(ERROR) << LOG_BADGE("HelloWorldPrecompiled") << LOG_DESC(" unkown func ")
  66. << LOG_KV("func", func);
  67. out = abi.abiIn("", u256(CODE_UNKNOW_FUNCTION_CALL));
  68. }
  69. return out;
  70. }
2.2.5 注册合约并编译源码
  • 注册开发的预编译合约。修改FISCO-BCOS/cmake/templates/UserPrecompiled.h.in,在下面的函数中注册HelloWorldPrecompiled合约的地址。默认已有,取消注释即可。
  1. void dev::blockverifier::ExecutiveContextFactory::registerUserPrecompiled(dev::blockverifier::ExecutiveContext::Ptr context)
  2. {
  3. // Address should in [0x5001,0xffff]
  4. context->setAddress2Precompiled(Address(0x5001), std::make_shared<dev::precompiled::HelloWorldPrecompiled>());
  5. }
  • 编译源码。请参考这里,安装依赖并编译源码。

注意:实现的HelloWorldPrecompiled.cpp和头文件需要放置于FISCO-BCOS/libprecompiled/extension目录下。

  • 搭建FISCO BCOS联盟链。 假设当前位于FISCO-BCOS/build目录下,则使用下面的指令搭建本机4节点的链指令如下。更多选项参考这里
  1. bash ../tools/build_chain.sh -l "127.0.0.1:4" -e bin/fisco-bcos

三 调用

从用户角度,预编译合约与solidity合约的调用方式基本相同,唯一的区别是solidity合约在部署之后才能获取到调用的合约地址,预编译合约的地址为预分配,不用部署,可以直接使用。

3.1 使用控制台调用HelloWorld预编译合约

在控制台contracts/solidity创建HelloWorldPrecompiled.sol文件,文件内容是HelloWorld预编译合约的接口声明,如下

  1. pragma solidity ^0.4.24;
  2. contract HelloWorldPrecompiled{
  3. function get() public constant returns(string);
  4. function set(string n);
  5. }

使用编译出的二进制搭建节点后,部署控制台v1.0.2以上版本,然后执行下面语句即可调用 ../../_images/call_helloworld.png

3.2 solidity调用

我们尝试在Solidity合约中创建预编译合约对象并调用其接口。在控制台contracts/solidity创建HelloWorldHelper.sol文件,文件内容如下

  1. pragma solidity ^0.4.24;
  2. import "./HelloWorldPrecompiled.sol";
  3. contract HelloWorldHelper {
  4. HelloWorldPrecompiled hello;
  5. function HelloWorldHelper() {
  6. // 调用HelloWorld预编译合约
  7. hello = HelloWorldPrecompiled(0x5001);
  8. }
  9. function get() public constant returns(string) {
  10. return hello.get();
  11. }
  12. function set(string m) {
  13. hello.set(m);
  14. }
  15. }

部署HelloWorldHelper合约,然后调用HelloWorldHelper合约的接口,结果如下 ../../_images/call_helloworldHelper.png