Skip to content

Latest commit

 

History

History
317 lines (194 loc) · 15.1 KB

智能合约.md

File metadata and controls

317 lines (194 loc) · 15.1 KB

ChainX 智能合约

English version

运行节点

1. 接入测试网

请参考加入ChainX测试网的相关说明。

完成相关配置后,应保证节点同步到最新,钱包相应配置完成。

2. 运行本地节点

请参考ChainX Dev模式的相关说明。

完成相应配置后,请保证已经出块超过150个区块,因为150个块后才会对Alice发放第一层次奖励。

若需要反复测试,可以对超过150个块后的区块数据进行备份,若需重新启动,则只需要删除老数据目录,使用备份数据目录替换即可。

ChainX上的Substrate Contracts 智能合约平台

Substrate 作为第一个区块链领域的技术框架,让开发者能够专注于链的运行时逻辑,而不用再花费大量的时间精力构建区块链底层的基础设施。此外, Substrate 默认提供了很多功能模块,比如 Staking, Consensus, 方便框架使用者根据自己的需求进行自由组合和定制。 合约模块就是其中的一个功能模块,不管是任何一条基于 Substrate 技术的独立链,还是未来的平行链, 只要集成了合约模块,就可以成为一个智能合约平台。

本次 ChainX 智能合约平台的主要实现方式就是集成 Substrate 的合约模块,并进行适配。 ChainX 的合约功能与 Substrate 默认的合约模块主要区别如下:

  1. 取消合约存储收费的设计。 合约存储收费简单来说就是当合约部署到链上以后, 根据该合约在链上所占存储的大小和该存储的占用时间收取一定的费用,当合约账户因为余额不够无法支持存储费用时,合约就会被删除,甚至可能无法恢复。 即使合约被删除后可以恢复,目前的合约恢复可操作性也是极低,可能会对目前的合约开发造成极大的困扰。因此,我们目前决定暂时取消合约存储收费,只收取合约调用的 Gas 费用, 也就是与目前以太坊的收费设计一致。当合约存储收费的模型成熟后,可以重新启用这个设计。

  2. 使用 ChainX ink! 编写智能合约

与Gas相关的参数配置

Schedule {
    version: 0, // 配置版本
    put_code_per_byte_cost: 200, // 设置wasm代码时,每字节需要的Gas
    grow_mem_cost: 1,  // 单页中内存增值需要的Gas
    regular_op_cost: 1,  // 普通操作符Op需要的Gas
    return_data_per_byte_cost: 1, // 返回值每字节消耗的Gas,因此对于合约见互相调用应仔细设计这个值
    event_data_per_byte_cost: 20,  // 合约中 event 每字节消耗的Gas
    event_per_topic_cost: 1, // 合约中 event 每个topic消耗的Gas
    event_base_cost: 1, // 合约中每个event要消耗的基础Gas,例如每打一个event就要先减少这个值的Gas
    call_base_cost: 60000, // 调用合约或合约调用合约消耗的基础Gas,例如每调用一个合约函数需要减少这个值的Gas
    instantiate_base_cost: 200000, // 合约初始化(实例化)消耗的基础Gas
    sandbox_data_read_cost: 1, // 合约中读取一个字节消耗的Gas
    sandbox_data_write_cost: 1, // 合约中写入一个字节消耗的Gas
    max_event_topics: 4,  // 一次调用中最多可以打的event个数
    max_stack_height: 64 * 1024, // 合约中栈的最大值
    max_memory_pages: 16, // 合约执行中最大的页数
    max_table_size: 16 * 1024, // 合约中最大数据结构表数
    enable_println: false, // 是否允许合约中出现 print
    max_subject_len: 32, // 最大的PRNG subject数
}

当前ChainX中默认的Gas Price 为5

其中请留意put_code_per_byte_costcall_base_costinstantiate_base_cost。因此在ChainX中:

  • 设置WASM合约代码的代价比较大,且在不同的环境下,编译出现的合约会不一致,因此:
    • 强烈建议使用ChainX提供的docker编译环境进行统一编译,以保证相同代码编译的合约一致。
    • 主网与测试网的上传限制不同:
      • 主网只允许开发者在测试网测试充分后,通过提交到议会,由议会统一审核编译上传wasm代码,以保证相同的代码一定是相同的wasm
      • 测试网没有上传代码的限制
  • 实例化一个合约的代价稍大,因此建议合约开发者尽量重用已实例化过的合约实例
  • 调用一个合约的代价一般,因此建议合约开发者精简自己的合约,集中在一个合约中处理逻辑。
  • Event最多只能打4个,因此需要合约开发者小心设计Event。

编译合约

WASM合约与Eth的solidity合约不同,相同的代码在不同的平台上可能编译出不同的wasm合约,因此强烈推荐使用ChainX提供的docker环境以进行统一规范。

// TODO

部署及调用合约

对于部署及调用合约,请参考ChainX的钱包相关部分:

开发调试合约

我们强烈建议:

  1. 合约开发者在ChainX Dev 节点下开发调试合约,因为很多错误信息只会在节点日志中出现。

  2. 在Dev模式下可以在合约中使用env.println,而在主网和测试网中一定要将合约中的env.println删除。

  3. 合约无论是执行还是方法的返回值都可以通过RPC在不打包的情况下调用,建议合约开发者可以先使用rpc调用模拟合约执行情况

一些异常错误

日志中与合约相关的日志均含有[runtime|xrml_xcontracts]字段,因此若只关心合约及合约执行结果,只需要对日志grep:

  • tail -f log/chainx.log | egrep "xcontracts|apply_extrinsic"

若设置,部署,调用合约不成功,可以参考如下问题列表:

1. 设置合约代码相关

  1. 合约部署时gas limit 提供的不够

    [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: there is not enough gas for storing the code

    因为ChainX中put_code_per_byte_cost设置得比较大,因此合约部署者应注意部署合约时提供的gas limit是否足够。一般情况下应大于len(wasm) * put_code_per_byte_cost

  2. wasm合约时,在组件间调用时因wasm过大,导致wasm被裁剪,部署不完整wasm

    [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: Can't decode wasm code

    此时请检查组件调用是否会裁剪wasm

  3. wasm合约已经存在于链上,对于相同的合约,不应该重复设置代码

    [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: the code is already stored on chain

    开发者自己编写工具时,请一定在上传代码前通过sdk的相关接口检查合约是否已经在链上

  4. 合约中含有ext.print,在自己测试能部署,在测试网与主网上不能部署

    [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: module imports `ext_println` but debug features disabled

    请注意在测试网和主网中,一定要将ext.print从合约中删除,或者使用条件编译控制

2. 部署合约代码相关

  1. 合约实例已经存在,请勿重复实例化。

    [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: Alive contract or tombstone already exists

    在ChainX合约(Substrate Contracts)模型中,生成合约地址的方式为:

    hash(code_hash + hash(input_data) + deployer)

    因此相同的部署者对于相同的一份合约使用相同的实例化参数,最后的合约结果都是一样的。若需要用相同的wasm代码,相同的参数实例化另一个实例,请换一个账户

  2. 参数传递错误,或者在实例化过程中崩溃,无法实例化

    [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: during execution|Failed to invoke an exported function for some reason|wrong selector, decode params fail or inner error

    请检查参数和合约代码,如:

    • 实例化接收的参数是u128,但是传递过去的是u64
    • 合约中的存储未初始化就进行加减操作/溢出/有panic异常...
  3. 部署达到了gas limit

    错误见下文

3. 调用合约代码相关

  1. 合约调用或内部崩溃

    [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: during execution|Failed to invoke an exported function for some reason|wrong selector, decode params fail or inner error
    1. 调用合约传递的selector不匹配,或参数编码错误

      selector请一定按照编译合约生成的的abi.json或者old_abi.json去调用,若使用不存在的selector则会调用不成功。编码错误同理

    2. 调用的方法中出现异常,如使用未初始化存储

      struct Incrementer {
      	value: storage::Value<u32>,
      }
      impl Deploy for Incrementer {
          fn deploy(&mut self) {
              // not init value
      	}
      }
      impl Incrementer {
         /// Flips the current state of our smart contract.
         pub(external) fn inc(&mut self, by: u32) {
             // 在日志中能看得到这一句日志
             env.println(&format!("Incrementer::inc by = {:?}", by));
             self.value += by;  // 调用未初始化的存储
         }
      }

      因此需要调试是因为参数/selector错误还是合约内部出错,请在合约内部打日志即可判定

    3. 溢出或panic

      具体请参考代码

  2. 调用过程中达到了gas limit

    [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: during execution|Failed to invoke an exported function for some reason|reach gas limit

    请通过rpc接口chainx_contractCall在非打包过程中去尝试得到适合的gas limit,建议覆盖合约执行中最远的执行分支。

    通过grep日志:

    [runtime|xrml_xcontracts::gas] [refund_unused_gas]|account:88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee (5SjUJPJJ...)|gas_payment:2500000|refund:0|real cost:2500000|gas_spent:500000

    可以看到这次调用中的gas耗费,其中

    1. gas_paymentgas_limit*gas_price的值,是调用者暂存的PCX
    2. refund是执行完成后返回的PCX
    3. real cost是真实消耗的PCX
    4. gas_spent是消耗的gas

    注意该部分消耗指的是在合约中的Gas执行的消耗,真实支付的PCX还要再加上调用合约方法的外部手续费。

ChainX智能合约中的跨链资产

当前ChainX智能合约中只支持比特币

ChainX 智能合约中多币种的实现方式

由于Substrate Contracts智能合约平台模型与以太坊智能合约模型相似,对于多币种的处理是“本币+代币”模型。

针对这种模型,ChainX的智能合约上的跨链资产采用合约代币模型,因此在ChainX上PCX将会类似以太坊一样对智能合约提供gas及本币流通的价值,对于ChainX上的跨链资产以合约代币的形式引入。

使用代币模型而不是将多币种直接引入合约基于如下考虑:

  1. 以太坊代币合约已经比较成熟,因此对于合约开发者在代币模型下可以比较容易的将以太坊合约迁移到ChainX智能合约平台上。
  2. 若在合约中引入多币种的概念,将会极大修改Substrate Contracts的模型,容易引入非预期问题。

当前ChainX实现智能合约中的比特币采用XRC20模型,根据合约开发者的反馈,将来也可采用其他标准。目前已经考虑的代币模型有:

  1. XRC20(原以太坊ERC20)
  2. XRC721(原以太坊ERC721)
  3. XRC777(原以太坊ERC777)

ChainX资产中的多币种与合约中代币的转换

ChainX上的比特币X-BTC

当前X-BTC对应于智能合约平台中采用的代币模型为XRC20。用户可以自由发起交易让ChainX的X-BTC与合约平台中的XRC20互相转换。

xrc20模型

其中ChainX修改了ERC20合约标准(XRC20),添加了issuedestroy两个接口。并首先将一个合约部署到链上并实例化,同时在链上唯一指定了这个合约实例,因此除指定的实例以外的代币实例均不被ChainX的Runtime承认

其中:

  • issue 只能被Runtime中的交易convert_to_xrc20调用,不可通过直接调用合约方法调用。通过交易convert_to_xrc20调用后,将会把该用户的资产转移到合约实例下,并在合约中对该发送交易的账户自动发放相应的金额。
  • destroy 能被用户调用,用于将合约中的资产转移到ChainX资产模块中。其中首先销毁了合约中对应的代币,然后合约中会自动调用convert_to_assets将资产从合约中返回给用户,而convert_to_assets不可通过外部交易调用。

ChainX上的XRC20

XRC20 项目

部署于ChainX上的XRC20项目为:

https://github.com/chainx-org/xrc20

针对XRC20,ChainX已经提供了2个对应的RPC可进行操作:

ChainX钱包导入XRC20 abi

当前钱包还未集成XRC20的abi,因此需要开发者手动添加。

  1. 获取链上指定的XRC20-BTC合约实例地址:

    调用rpc chainx_contractXRCTokenInfo 可在返回值中看到链上已经存在的XRC20-BTC合约地址公钥

  2. 参考[合约功能部分|实例化合约](wallet#2. 实例化合约(部署合约))部分的第3节,通过“添加已存在的合约”的方式,将该ERC20-BTC合约地址的公钥填入,然后在XRC20项目中的target/abi.json的abi文件上传即可添加该合约实例。

主网与测试网-道

对于主网与测试网-道,该针对X-BTC的合约实例已经部署且被设置完成。合约开发者或用户只需要在合约平台上导入该项目中的abi 文件target/abi.json即可调用合约。其中该XRC20的地址可以通过rpc chainx_contractXRCTokenInfo 获取到。

ChainX Dev 节点

对于ChainX Dev 节点而言,该合约没有被内置。因此若需要调试和智能合约上的比特币相关操作,需要:

  1. 在ChainX Dev 中设置XRC20-BTC 合约
  2. 发放测试使用的假X-BTC

xrc20项目中已经提供了一个脚本执行这两件事情,详情请参考README。

该脚本默认使用Alice的私钥(见ChainX Dev模式的开头部分)执行。请注意执行该脚本至少需要等待已经出了150个区块后(Alice才会具备资产执行交易)。

若PCX不足,该脚本也提供了从Alice验证者领取奖池的操作。

钱包功能

ChainX新钱包中提供了ChainX的跨链资产X-BTC与合约中的XRC20-BTC互相划转的功能,参考链接

ChainX 智能合约的部署即测试

智能合约的部署及调用请参考ChainX钱包部分。

测试请参考ChainX Dev模式|调试合约部分