2中如何添加cclink模块_【Substrate入门】在Runtime中添加智能合约模块

1d00b26828c4562954d16faafa5cc4dd.png

文章内容持续更新......

前文《【Substrate入门】在Runtime添加一个模块》中已经添加了一个很简洁的模块,仅仅是让我们有一个直观的认识,在实际项目开发中,还会有更复杂的一些开发,比如RPC API,本文旨在介绍一个典型的复杂的模块——智能合约模块pallet-contracts ,它覆盖了开发环节中可能遇到的棘手问题,有助于我们更深入地理解pallet的开发。

本文目标

掌握将一个复杂的pallet加入到runtime,以便后续我们能灵活自主地将官方造的轮子(pallet)加入到链当中。

操作步骤

  • 导入智能合约pallet
  • 添加智能合约pallet到runtime
  • 更新外节点
  • 升级runtime

前提条件

本文的操作是基于substrate-node-template v2.0.0和前端界面substrate-front-end-template构建的环境,如果我们还没有构建好,请参考之前的文章【Substrate入门】搭建第一条substrate链

开始吧!

一、导入智能合约pallet

先预览一下我们即将要开发的涉及到的文件与目录:

substrate-node-template
|
+-- runtime
|   |
|   +-- Cargo.toml   <-- 在包管理文件中将合约模块加入到依赖项中
|   |
|   +-- build.rs
|   |
|   +-- src
|      |
|      +-- lib.rs   <-- 多处需要修改
|
+-- pallets
|
+-- scripts
|
+-- node            <-- 多处需要修改
|
+-- ...

1、首先将pallet-contracts加入到Cargo.toml中,打开runtime/Cargo.toml 文件:

[dependencies]
#--snip--
pallet-contracts = { version = '2.0.0', default_features = false }
pallet-contracts-primitives = { version = '2.0.0', default_features = false }

与其他pallet一样,智能合约pallet也有一个std功能,在同一个文件中找到并添加:

[features]
default = ['std']
std = [
    #--snip--
    'pallet-contracts/std',
    'pallet-contracts-primitives/std',
    #--snip--
]
简单地理解一下std与no_std的区别:有些嵌入式设备或者底层开发时没有文件或者网络系统,rust作为一个系统编程语言,既可以做系统级别的开发,也可以支持应用层的开发(如读写文件),std里包含了常用的应用层开发的功能,而no_std则没有这些功能,在cargo编译的时候可以指定是否加载std。在substrate开发中,基本上每个pallet都需要std的支持。

检查一下依赖项是否能编译通过:

SKIP_WASM_BUILD=1 cargo check

二、添加智能合约pallet到runtime

1、打开文件runtime/src/lib.rs ,添加如下代码:

// These time units are defined in number of blocks.
/* --snip-- */

/*** 添加这一段 开始 ***/
// Contracts price units.
pub const MILLICENTS: Balance = 1_000_000_000;
pub const CENTS: Balance = 1_000 * MILLICENTS;
pub const DOLLARS: Balance = 100 * CENTS;
/*** 添加这一段 结束 ***/
impl pallet_timestamp::Trait for Runtime {
    /* --snip-- */
}

/*** 添加这一段 开始 ***/
parameter_types! {
    pub const TombstoneDeposit: Balance = 16 * MILLICENTS;
    pub const RentByteFee: Balance = 4 * MILLICENTS;
    pub const RentDepositOffset: Balance = 1000 * MILLICENTS;
    pub const SurchargeReward: Balance = 150 * MILLICENTS;
}

impl pallet_contracts::Trait for Runtime {
    type Time = Timestamp;
    type Randomness = RandomnessCollectiveFlip;
    type Currency = Balances;
    type Event = Event;
    type DetermineContractAddress = pallet_contracts::SimpleAddressDeterminer<Runtime>;
    type TrieIdGenerator = pallet_contracts::TrieIdFromParentCounter<Runtime>;
    type RentPayment = ();
    type SignedClaimHandicap = pallet_contracts::DefaultSignedClaimHandicap;
    type TombstoneDeposit = TombstoneDeposit;
    type StorageSizeOffset = pallet_contracts::DefaultStorageSizeOffset;
    type RentByteFee = RentByteFee;
    type RentDepositOffset = RentDepositOffset;
    type SurchargeReward = SurchargeReward;
    type MaxDepth = pallet_contracts::DefaultMaxDepth;
    type MaxValueSize = pallet_contracts::DefaultMaxValueSize;
    type WeightPrice = pallet_transaction_payment::Module<Self>;
}
/*** 添加这一段 结束 ***/

将智能合约pallet加载到自启动区construct_runtime!

打开runtime/src/lib.rs, 搜索construct_runtime!

construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        /* --snip-- */

        Contracts: pallet_contracts::{Module, Call, Config, Storage, Event<T>}, <-- 添加这一行
    }
);

检查一下runtime是否能编译通过:

SKIP_WASM_BUILD=1 cargo check -p node-template-runtime

公开合约API

打开runtime/Cargo.toml

[dependencies]
#--snip--
pallet-contracts-rpc-runtime-api = { version = '0.8.0', default-features = false } <-- 添加这一行

同一文件中找到并添加:

[features]
default = ['std']
std = [
    #--snip--
    'pallet-contracts-rpc-runtime-api/std', <-- 添加这一行
]

我们需要将返回类型添加到runtime,将它与其他use语句一起添加:

use pallet_contracts_rpc_runtime_api::ContractExecResult;

现在,我们准备实现智能合约的Runtime API,搜索impl_runtime_apis!,在其中添加如下代码:

impl_runtime_apis! {
   /* --snip-- */

   /*** 添加这一段 开始 ***/
    impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber>
        for Runtime
    {
        fn call(
            origin: AccountId,
            dest: AccountId,
            value: Balance,
            gas_limit: u64,
            input_data: Vec<u8>,
        ) -> ContractExecResult {
            let (exec_result, gas_consumed) =
                Contracts::bare_call(origin, dest.into(), value, gas_limit, input_data);
            match exec_result {
                Ok(v) => ContractExecResult::Success {
                    flags: v.flags.bits(),
                    data: v.data,
                    gas_consumed: gas_consumed,
                },
                Err(_) => ContractExecResult::Error,
            }
        }

        fn get_storage(
            address: AccountId,
            key: [u8; 32],
        ) -> pallet_contracts_primitives::GetStorageResult {
            Contracts::get_storage(address, key)
        }

        fn rent_projection(
            address: AccountId,
        ) -> pallet_contracts_primitives::RentProjectionResult<BlockNumber> {
            Contracts::rent_projection(address)
        }
    }
   /*** 添加这一段 结束 ***/
}

为了确保每一步的修改不会出现错误,再次检查一下runtime是否能编译通过:

SKIP_WASM_BUILD=1 cargo check -p node-template-runtime

二、更新外节点

到这一步,我们已经完成了向Runtime中添加智能合约pallet的工作。现在,我们将注意力转向外部节点,该外部节点通常需要一些相应的更新。对于智能合约pallet,我们将添加自定义RPC API和创始配置。

添加RPC API扩展

公开了正确的运行时API之后,现在我们可以将RPC添加到节点的服务中,以调用该运行时API。因为我们现在在外部节点上工作,所以我们不需要构建no_std,也不必维护专用std功能。

打开node/Cargo.toml文件,添加如下依赖项:

[dependencies]
jsonrpc-core = '15.0.0'
structopt = '0.3.8'
#--snip--
# *** 添加下面两行 ***
pallet-contracts = '2.0.0'
pallet-contracts-rpc = '0.8.0'

Substrate提供了一个RPC与我们的节点进行交互,但是,默认情况下,它不包含对智能合同pallet的访问。要与该pallet交互,我们必须扩展现有的RPC并添加智能合约pallet及其API。

打开文件node/src/rpc.rs , 添加如下代码:

use node_template_runtime::{opaque::Block, AccountId, Balance, Index, BlockNumber};
use pallet_contracts_rpc::{Contracts, ContractsApi};
/// Instantiate all full RPC extensions.
pub fn create_full<C, P>(
    deps: FullDeps<C, P>,
) -> jsonrpc_core::IoHandler<sc_rpc::Metadata> where
    /* --snip-- */
    C: Send + Sync + 'static,
    C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>,
    /*** 添加下面一行 ***/
    C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber>,
    /* --snip-- */
{
    /* --snip-- */

    // Extend this RPC with a custom API by using the following syntax.
    // `YourRpcStruct` should have a reference to a client, which is needed
    // to call into the runtime.
    // `io.extend_with(YourRpcTrait::to_delegate(YourRpcStruct::new(ReferenceToClient, ...)));`

    /*** 添加这一段 开始 ***/
    io.extend_with(
        ContractsApi::to_delegate(Contracts::new(client.clone()))
    );
    /*** 添加这一段 结束 ***/
    io
}

创世配置

并不是所有pallet都具有创世配置,但是如果有的话,我们可以查看开发文档。例如:pallet_contracts::GenesisConfig文档描述了我们需要为智能合约pallet定义的所有字段。

创世配置是在node/src/chain_spec.rs文件中进行修改。我们需要修改此文件以ContractsConfig在顶部包含类型和合约价格单位:

use node_template_runtime::ContractsConfig;

然后在testnet_genesis函数中,我们需要将合​​同配置添加到返回的GenesisConfig对象中,如下所示:

/// Configure initial storage state for FRAME modules.
fn testnet_genesis(
    wasm_binary: &[u8],
    initial_authorities: Vec<(AuraId, GrandpaId)>,
    root_key: AccountId,
    endowed_accounts: Vec<AccountId>,
    enable_println: bool, // <-- 修改这一行,去除前面的下划线
) -> GenesisConfig {
    GenesisConfig {
        /* --snip-- */

        /*** 添加这一段 开始 ***/
        pallet_contracts: Some(ContractsConfig {
            current_schedule: pallet_contracts::Schedule {
                    enable_println,
                    ..Default::default()
            },
        }),
        /*** 添加这一段 结束 ***/
    }
}

三、升级Runtime

现在,我们可以编译并运行具有智能合同模块的节点了:

WASM_BUILD_TOOLCHAIN=nightly-2020-10-05 cargo build --release

编译完成后,在开发模式下启动临时节点:

./target/release/node-template --dev --tmp

我们看到节点能正常启动起来就说明链升级成功了。

添加其他FRAME中的pallet

在本文中,我们专门介绍了如何导入智能合约pallet,但是每个pallet都会有所不同。不用担心,我们可以随时参考演示的Substrate节点的Runtime,该Runtime几乎包括FRAME中的每个pallet。在Substrate节点的runtime/Cargo.toml文件中,我们可以看到每个不同pallet的导入示例,在lib.rs文件中,我们将找到如何将每个pallet添加到Runtime,基本上复制粘贴到我们的Runtime即可。

进一步学习

  • 关于在自己的程序包中编写Runtime pallet的极简教程
  • 接下来,我们的节点就可以运行智能合约了,学习有关Substrate ink!智能合约语言
  • Substrate Recipes提供了有关编写运行时API和自定义RPC的详细教程,如本文中探讨的内容
  • 了解Chain Spec文件以自定义我们的Genesis配置。

小结

待小结


参考:

添加一个模块 · Substrate Developer Hub​substrate.dev
cd06e1ee645052336c1e8ed48b8b7789.png
pallet_contracts - Rust​substrate.dev
c81b664f6780053d5f4dd48d24150a50.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值