heco生态链流动性挖矿dapp系统开发部署(成熟技术)

2022-10-24 14:33:33 浏览数 (1)

火币生态链 Huobi ECO Chain(HECO)是一个去中心化高效节能公链,也是火币开放平台推出的首个产品,在支撑高性能交易得基础上,实现智能合约的兼容。

Heco是火币开放平台的公链基础设施,未来将成为承载用户、资产和应用的基础平台。

概述

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/nervosnetwork/riscv-contract-tutorials.git

$ 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:''

     }

   }

 }

0 人点赞