其他
一文带你读懂如何在ChainX上部署智能合约
运行节点
1. 接入测试网
2. 运行本地节点
ChainX 上的 Substrate Contracts 智能合约平台
取消合约存储收费的设计。 合约存储收费简单来说就是当合约部署到链上以后, 根据该合约在链上所占存储的大小和该存储的占用时间收取一定的费用,当合约账户因为余额不够无法支持存储费用时,合约就会被删除,甚至可能无法恢复。 即使合约被删除后可以恢复,目前的合约恢复可操作性也是极低,可能会对目前的合约开发造成极大的困扰。因此,我们目前决定暂时取消合约存储收费,只收取合约调用的 Gas 费用, 也就是与目前以太坊的收费设计一致。当合约存储收费的模型成熟后,可以重新启用这个设计。 使用适配后的 chainx-org/ink 编写合约。 chainx-org/ink fork 自 paritytech/ink, 主要改动在于引入了 DefaultXrmlTypes
替代DefaultRrmlTypes
来适配 ChainX 的 runtime:
关联类型 Balance
从u128
改为u64
.适配关联类型 Call
以支持 PCX 转账和将跨链资产划转过去的 XRC20 Token 转回 ChainX 跨链资产, 合约开发者可以基于 PCX 和 ChainX 的跨链资产设计自己的 Dapp。
substrate-contracts-workshop: ink 官方教程 polkaworld-org/workshop: polkaworld 合约相关技术讲座
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数
}
put_code_per_byte_cost
,call_base_cost
,instantiate_base_cost
。因此在 ChainX 中:设置 WASM 合约代码的代价比较大,因此建议合约开发者仔细设计及模块化自己的合约代码,不鼓励过多的设置合约代码 实例化一个合约的代价稍大,因此建议合约开发者尽量重用已实例化过的合约实例 调用一个合约的代价一般,因此建议合约开发者精简自己的合约,集中在一个合约中处理逻辑。 Event 最多只能打4个,因此需要合约开发者小心设计 Event。
部署及调用合约
合约功能部分 合约开发独立部署组件开发调试合约
开发调试合约
合约开发者在 ChainX Dev 节点下开发调试合约,因为很多错误信息只会在节点日志中出现。 在 Dev 模式下可以在合约中使用 env.println
,而在主网和测试网中一定要将合约中的env.println
删除。合约无论是执行还是方法的返回值都可以通过 RPC 在不打包的情况下调用,建议合约开发者可以先使用 rpc 调用模拟合约执行情况
一些异常错误
[runtime|xrml_xcontracts]
字段,因此若只关心合约及合约执行结果,只需要对日志 grep:tail -f log/chainx.log | egrep "xcontracts|apply_extrinsic"
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
wasm 合约时,在组件间调用时因 wasm 过大,导致 wasm 被裁剪,部署不完整 wasm [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: Can't decode wasm code 此时请检查组件调用是否会裁剪 wasm wasm 合约已经存在于链上,对于相同的合约,不应该重复设置代码 [runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: the code is already stored on chain 开发者自己编写工具时,请一定在上传代码前通过 sdk 的相关接口检查合约是否已经在链上。 合约中含有 ext.print
,在自己测试能部署,在测试网与主网上不能部署[runtime|chainx_runtime::xexecutive|183L] [apply_extrinsic] failed: module imports `ext_println` but debug features disabled 请注意在测试网和主网中,一定要将 ext.print
从合约中删除,或者使用条件编译控制
2. 部署合约代码相关
合约实例已经存在,请勿重复实例化。 [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 代码,相同的参数实例化另一个实例,请换一个账户 参数传递错误,或者在实例化过程中崩溃,无法实例化 [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. 调用合约代码相关
合约调用或内部崩溃 [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 调用合约传递的 selector 不匹配,或参数编码错误 selector 请一定按照编译合约生成的的 abi.json
或者old_abi.json
去调用,若使用不存在的 selector 则会调用不成功。编码错误同理调用的方法中出现异常,如使用未初始化存储 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 错误还是合约内部出错,请在合约内部打日志即可判定 溢出或 panic 具体请参考代码 调用过程中达到了 gas limit gas_payment
为gas_limit*gas_price
的值,是调用者暂存的 PCXrefund
是执行完成后返回的 PCXreal cost
是真实消耗的PCX
gas_spent
是消耗的 gas
chainx_contractCall
在非打包过程中去尝试得到适合的gas limit
,建议覆盖合约执行中最远的执行分支。ChainX 智能合约中的跨链资产
以太坊代币合约已经比较成熟,因此对于合约开发者在代币模型下可以比较容易的将以太坊合约迁移到 ChainX 智能合约平台上。 若在合约中引入多币种的概念,将会极大修改 Substrate Contracts 的模型,容易引入非预期问题。
XRC20 (原以太坊 ERC20) XRC721 (原以太坊 ERC721) XRC777 (原以太坊 ERC777)
issue
,destroy
两个接口。并首先将一个合约部署到链上并实例化,同时在链上唯一指定了这个合约实例,因此除指定的实例以外的代币实例均不被 ChainX 的 Runtime 承认issue
只能被 Runtime 中的交易convert_to_xrc20
调用,不可通过直接调用合约方法调用。通过交易convert_to_xrc20
调用后,将会把该用户的资产转移到合约实例下,并在合约中对该发送交易的账户自动发放相应的金额。destroy
能被用户调用,用于将合约中的资产转移到 ChainX 资产模块中。其中首先销毁了合约中对应的代币,然后合约中会自动调用convert_to_assets
将资产从合约中返回给用户,而convert_to_assets
不可通过外部交易调用。
ChainX 上的 XRC20
chainx_contractXRCTokenInfo chainx_contractXRC20Call
ChainX 钱包导入 XRC20 abi
获取链上指定的 XRC20-BTC 合约实例地址: 调用 rpc chainx_contractXRCTokenInfo
可在返回值中看到链上已经存在的 XRC20-BTC 合约地址公钥参考[合约功能部分|实例化合约]( wallet#2. 实例化合约(部署合约))部分的第3节,通过“添加已存在的合约”的方式,将该 ERC20-BTC 合约地址的公钥填入,然后在 XRC20 项目中的 target/abi.json
的 abi 文件上传即可添加该合约实例。
主网与测试网-道
target/abi.json
即可调用合约。其中该XRC20的地址可以通过rpc chainx_contractXRCTokenInfo
获取到。ChainX Dev 节点
在 ChainX Dev 中设置 XRC20-BTC 合约 发放测试使用的假 X-BTC
xrc20
项目中已经提供了一个脚本执行这两件事情,详情请参考 README。