共计 4061 个字符,预计需要花费 11 分钟才能阅读完成。
火币生态链 Huobi ECO Chain(HECO)是一个去中心化高效节能公链,也是火币开放平台推出的首个产品,在撑持高性能交易得根底上,实现智能合约的兼容。
Heco 是火币开放平台的公链基础设施,heco 链智能合约 dapp 零碎开发唯 +hkkf5566,将来将成为承载用户、资产和利用的根底平台。
概述
RISC-V service 为 Huobi Chain 提供了一个反对 RISC-V 指令集的虚拟机服务。用户能够通过该服务自行部署和运行合约,实现弱小的自定义性能。
一个 RISC-V 合约的实质是一个 Linux 的 ELF 可执行文件,应用虚拟机运行该合约等同于 Linux 环境下在单核 CPU 下运行这个可执行文件。
实践上任何提供了 RISC-V 后端的语言均能够用来开发合约。就生成代码的体积和品质(运行过程中 cycle 的耗费)而言,目前最成熟的工具是 riscv-gcc。
本文将以 C 语言开发的一个 ERC20 Token 和 Bank 合约为例,为你展现如何编写、部署、调用、测试一个 RISC-V 合约。
Echo 合约示例
咱们首先来看一个简略的 echo 合约:
include <pvm.h>
int main() {
char args[100] = {0};
uint64_t args_len = 0;
pvm_load_args(args, &args_len);
pvm_ret(args, args_len);
return 0;
}
该合约的作用是将参数的内容原样返回。
将该 C 代码通过 riscv-gcc 编译生成的二进制文件即为咱们的合约。
运行合约时,从 main 函数开始。当 main 函数返回值为 0 时,认为合约执行胜利,否则合约执行失败。
留神,这个合约中咱们引入了 pvm.h,应用了其中的 pvm_load_args 和 pvm_ret 函数。pvm.h 这个文件中蕴含了咱们与链交互所须要的所有函数。这些函数是通过零碎调用实现的,咱们将在下节进行具体解说。
pvm
pvm 反对合约向 ckb-vm 内部发动调用的零碎函数汇合。在 ckb-vm 外部,你无奈间接向链发动任何调用,包含获取 Block,Receipt 这样罕用的办法。所有的对链申请,都必须通过 ckb-vm 的 ecall 指令集实现。pvm 替开发者封装了所有对链申请的逻辑,导出为 C 函数,供合约调用
零碎调用
因为 CKB-VM 只是一个 RISC-V 指令集解释器。要实现合约的简单逻辑,必然要与链进行交互,如解析参数,返回后果,获取链 / 交易上下文,操作合约状态等。因而咱们在 risc-v service 中用零碎调用实现了这些交互性能。
上面是 pvm_load_args 函数的实现。调用该函数时,虚构机会依据寄存器的状态,调用虚拟机内部对应性能的实现函数,并将实现后果通过寄存器和内存写回虚拟机。例如上面的 pvm_load_args 调用时,会将交易中的合约调用参数写到虚拟机外部的内存,而后将内存起始地址和参数长度写到对应的寄存器。
static inline long
__internal_syscall(long n, long _a0, long _a1, long _a2, long _a3, long _a4, long _a5)
{
register long a0 asm("a0") = _a0;
register long a1 asm("a1") = _a1;
register long a2 asm("a2") = _a2;
register long a3 asm("a3") = _a3;
register long a4 asm("a4") = _a4;
register long a5 asm("a5") = _a5;
register long syscall_id asm("a7") = n;
asm volatile ("scall": "+r"(a0) : "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(syscall_id));
return a0;
}
define syscall(n, a, b, c, d, e, f) \
__internal_syscall(n, (long)(a), (long)(b), (long)(c), (long)(d), (long)(e), (long)(f))
int pvm_load_args(uint8_t data, uint64_t len)
{
return syscall(SYSCODE_LOAD_ARGS, data, len, 0, 0, 0, 0);
}
RISC-V service 中提供的零碎调用有 4 类:
debug 工具。pvm_debug 提供了虚拟机外部的 debug 工具,用户能够打印一段任意的 bytes,不便合约进行调试
入参出参。pvm_load_args 和 pvm_ret 别离提供了合约的入参和出参性能,用户通过前者获取合约调用参数,通过后者返回合约执行后果。合约的入参和出参均为任意的 bytes
获取交易上下文。例如通过 pvm_caller 获取调用该函数的地址,pvm_block_height 获取以后块高度。
链操作:
pvm_set_storage 和 pvm_get_storage 能够用来操作合约的状态空间。每个合约领有独立的地址,在该地址下领有独立的状态空间,能够把这个状态空间了解成一个 kv 数据库,用户能够在外面保留任意的数据。合约只能拜访和批改本人状态空间内的数据。能够把这个状态空间类比了解成以太坊的 contract storage。
pvm_contract_call 能够用来调用其它 RISC-V 合约,pvm_service_call 能够用来调用 huobi-chain 的其它 build-in service。
所有的零碎调用函数都在 pvm.h 文件中,外面有具体的函数文档,读者能够自行查阅。
ERC20 和 Bank 合约代码
了解了零碎调用后,咱们来看一个实在的 ERC20 合约和 Bank 合约的例子。
咱们将源码放到了 GitHub 上,读者能够将示例代码下载到本地进行查看和交互。
$ git clone https://github.com/nervosnetw…
$ cd riscv-contract-tutorials/bank
ERC20 合约 是一个合乎 ERC20 规范的 token 合约。本合约仅作为阐明合约性能之用,读者如有发行自定义资产的需要,倡议应用 huobi-chain 的原生资产模块,asset service。
Bank 合约实现了存钱、取钱、查余额等性能,展现了合约如何与其它合约互相操作(例如存钱时须要调用 ERC20 合约的 transfer_from 性能),该合约自身并没有什么现实意义,但能够很容易扩大成一个相似 EtherDelta 这样的 DEX 合约。
合约阐明:
序列化:因为合约的入参出参均为任意的 bytes,对于性能简单的合约,咱们可能须要引入一些序列化办法。在上述的代码中,咱们应用的是 JSON 格局,因为 JSON 应用宽泛,无 schema 限度,且可读性较好。用户也能够依据本人的需要(速度、可读性、大小),抉择适宜的序列化计划,如 rlp,protobuf,thrift,msgpack 等。
函数散发:合约执行的对立入口为 main 函数,用户如想在一个合约中实现许多不同的性能,能够自行在 main 函数中进行函数散发。上述示例中,即是通过 method 字段的内容来路由到不同的函数进行解决。
除了零碎合约外,咱们还用到了很多其余的 C 语言库来帮忙开发,具体内容能够参见 deps 文件夹。
编译
咱们应用 riscv-gcc 来将 C 源码编译成二进制文件。因为 riscv-gcc 工具编译较为简单,咱们提供了打包好的 docker 镜像供读者应用。编译示例如下:
读者能够在 bank 文件夹中运行:
$ make bin_docker
命令来应用 docker 进行编译,在 bin 文件夹下失去的两个二进制文件即为咱们的合约。
交互
RISC-V service 提供了两种 exec 和 call 两个交互接口。前者为写接口,能够操作链上数据,须要通过发交易,打包执行,后者为查问接口,能够通过链的 query 性能间接调用。
示例(持续应用方才的 client):
查问接口
await client.queryService({serviceName: ‘riscv’, method: ‘call’, payload: JSON.stringify({ address, args: JSON.stringify({method: ‘total_supply’}) })})
{isError: false, ret: ‘”1000000000″‘}
发交易
payload = JSON.stringify({address, args: JSON.stringify({method: ‘transfer’, recipient: ‘0000000000000000000000000000000000000000’, amount: 100})})
tx = await client.composeTransaction({method: ‘exec’, payload, serviceName: ‘riscv’})
txHash = await client.sendTransaction(account.signTransaction(tx))
receipt = await client.getReceipt(txHash)
{txHash:
‘9f18a395972012817c68611e86e182af72f33964ec86c629c8727a9ec1a79daa’,
height: ‘0000000000000386’,
cyclesUsed: ‘0000000000072306’,
events: [],
stateRoot:
‘7f95c8a6d338fbd64de764cdf1870cc60696c4e69af1656de279d7cded6026ed’,
response:
{serviceName: ‘riscv’,
method: 'exec',
response:{
code:'0x00',
succeed_data:'',
error_message:''
}
}
}