LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 新闻观点 > 在 Substrate 中为你的 runtime 添加合约模块

在 Substrate 中为你的 runtime 添加合约模块

2020-06-13 DotNews 来源:区块链网络


添加 Pallet(模块)

Substrate 节点模板[1]提供了最小的 runtime,你可以用它来快速构建自定义区块链。然而,为保持最小限度,它并不包括FRAME[2]中的大部分 Pallet(模块)。

本指南将向你展示如何将 Contracts pallet[3]添加到 runtime 中,以便让你的区块链支持 Wasm 智能合约。你可以按照类似的模式将其他 FRAME pallet 添加到你的 runtime,但需要注意的是,每个 pallet 在使用它时,所需的特定配置设置方面略有不同。

Install the Node Template

当你学习完教程创建你的第一条 Substrate 链[4],你应该已经在计算机上编译了Substrate Node Template[5]的v2.0.0-alpha.8版本, 如果还没有的话,请先完成教程。

如果你是有经验的开发人员,倾向于选择跳过该教程,建议你可以根据自述文件中的说明安装节点模板。

文档结构

现在,我们将修改substrate-node-template让它可以包含 contracts pallet。

选一个你喜欢的代码编辑器打开substrate-node-template。我们将编辑两个文件:runtime/src/lib.rs, andruntime/Cargo.toml。

substrate-node-template
|
+-- runtime
| |
| +-- Cargo.toml <-- One change in this file
| |
| +-- build.rs
| |
| +-- src
| |
| +-- lib.rs <-- Most changes in this file
|
+-- pallets
|
+-- scripts
|
+-- node
|
+-- ...

导入 Pallet Crate

添加合约模块所需要做的第一件事是在 runtime 的Cargo.toml文件中导入pallet-contractscrate。如果您想在 Cargo References 中找到合适的入门资料,则应查阅其正式文档[6]。

打开substrate-node-template/runtime/Cargo.toml,你会看到运行时具有的所有依赖关系的列表。例如,它依赖Balances pallet[7]:

runtime/Cargo.toml

[dependencies.balances]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
package = 'pallet-balances'
tag = 'v2.0.0-alpha.8'

Crate 功能

导入模块 Crate 时需要注意的一件事是,确保正确设置 Cratefeatures。在上面的代码片段中,你会注意到我们设置了default_features = false。如果您更仔细地浏览Cargo.toml文件,则会发现类似以下内容:

runtime/Cargo.toml

[features]
default = ['std']
std = [
'codec/std',
'client/std',
'sp-std/std',
'sp-io/std',
'balances/std',
'frame-support/std',
#--snip--
]

第二行将 runtime crate 的default功能定义为std。你可以想象,每个 pallet crate 都有类似的配置,定义了这个 crate 的默认功能。你的功能将确定应在下游依赖项上使用的功能。例如,上面的代码段应读取为:

此 Substrate runtime 的默认功能是std。如果为 runtime 启用了std功能,那么parity-scale-codec、primitives、client,和所有其他列出的依赖项也应使用其std功能。

这对于使 Substrate 运行时能够编译为本机二进制文件(支持 Rust`std`[8]),和 Wasm 二进制文件(不支持`no_std`[9])。

要查看这些功能实际上如何在运行时代码中使用,我们可以打开项目文件:

runtime/src/lib.rs

//!TheSubstrateNodeTemplateruntime.Thiscanbecompiledwith`#[no_std]`,readyforWasm.

#![cfg_attr(not(feature="std"),no_std)]
//`construct_runtime!`doesalotofrecursionandrequiresustoincreasethelimitto256.
#![recursion_limit="256"]

//MaketheWASMbinaryavailable.
#[cfg(feature="std")]
include!(concat!(env!("OUT_DIR"),"/wasm_binary.rs"));

//--snip--

可以看到,在文件顶部,我们定义了在不使用std功能时将使用no_std。

在下面几行中你会看见#[cfg(feature = "std")],在wasm_binary.rsimport 之上,这是一个标志,要求仅在启用std功能后才导入 WASM 二进制文件,这时会看到下面的几行。

导入 Contracts Pallet Crate

好的,既然我们已经了解了 crate 功能的基础知识,那么我们实际上可以导入 Contracts pallet 了。Contracts pallet 可能是 FRAME 中最复杂的模块,因此它是添加其他模块时可能涉及的一些棘手问题的一个很好的例子。

首先,我们将通过简单地复制现有模块并更改值来添加新的依赖项。因此,根据上面显示的balances导入,contracts导入将类似于:

runtime/Cargo.toml

[dependencies.contracts]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
package = 'pallet-contracts'
tag = 'v2.0.0-alpha.8'

[dependencies.contracts-primitives]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
package = 'pallet-contracts-primitives'
tag = 'v2.0.0-alpha.8'

与其他模块一样,Contracts pallet 具有std功能。当运行时使用其自己的std功能构建时,我们应该构建其std功能。将以下两行添加到运行时的std功能。

runtime/Cargo.toml

[features]
default = ["std"]
std = [
#--snip--
'contracts/std',
'contracts-primitives/std',
#--snip--
]

如果您忘记提前设置功能,则在构建到本机二进制文件时会出现类似以下错误:

error[E0425]:cannotfindfunction`memory_teardown`inmodule`sandbox`
-->~/.cargo/git/checkouts/substrate-7e08433d4c370a21/83a6f1a/primitives/sandbox/src/../without_std.rs:53:12
|
53|sandbox::memory_teardown(self.memory_idx);
|^^^^^^^^^^^^^^^notfoundin`sandbox`

error[E0425]:cannotfindfunction`memory_new`inmodule`sandbox`
-->~/.cargo/git/checkouts/substrate-7e08433d4c370a21/83a6f1a/primitives/sandbox/src/../without_std.rs:72:18
|
72|matchsandbox::memory_new(initial,maximum){
|

...

现在是时候检查一下是否可以正确编译所有内容:

cargocheck

添加 Contracts Pallet

现在,我们已经成功导入了 Contracts pallet crate,我们需要将其添加到运行时中。不同的 pallets(模块)将要求你use不同的对应物。对于 Contracts pallet,我们将使用Schedule类型。在运行开始时,将此行与其他pub use语句一起添加。

runtime/src/lib.rs

/***AddThisLine***/
///ImportingthecontractsScheduletype.
pubusecontracts::ScheduleasContractsSchedule;

实施 Contract Trait

每个 pallet(模块)都有运行时必须实现的称为Trait的配置特征。

要弄清楚我们需要为该 pallet(模块)具体实现什么,可以查看 FRAME`contracts::Trait` 文档[10]。对于我们的 runtime,实现将如下所示:

runtime/src/lib.rs

//Thesetimeunitsaredefinedinnumberofblocks.
/*--snip--*/

/***AddThisBlock***/
//Contractspriceunits.
pubconstMILLICENTS:Balance=1_000_000_000;
pubconstCENTS:Balance=1_000*MILLICENTS;
pubconstDOLLARS:Balance=100*CENTS;
/***EndAddedBlock***/
impltimestamp::TraitforRuntime{
/*--snip--*/
}

/***AddThisBlock***/
parameter_types!{
pubconstTombstoneDeposit:Balance=1*DOLLARS;
pubconstRentByteFee:Balance=1*DOLLARS;
pubconstRentDepositOffset:Balance=1000*DOLLARS;
pubconstSurchargeReward:Balance=150*DOLLARS;
}

implcontracts::TraitforRuntime{
typeTime=Timestamp;
typeRandomness=RandomnessCollectiveFlip;
typeCall=Call;
typeEvent=Event;
typeDetermineContractAddress=contracts::SimpleAddressDeterminer<Runtime>;
typeTrieIdGenerator=contracts::TrieIdFromParentCounter<Runtime>;
typeRentPayment=();
typeSignedClaimHandicap=contracts::DefaultSignedClaimHandicap;
typeTombstoneDeposit=TombstoneDeposit;
typeStorageSizeOffset=contracts::DefaultStorageSizeOffset;
typeRentByteFee=RentByteFee;
typeRentDepositOffset=RentDepositOffset;
typeSurchargeReward=SurchargeReward;
typeMaxDepth=contracts::DefaultMaxDepth;
typeMaxValueSize=contracts::DefaultMaxValueSize;
}
/***EndAddedBlock***/

我们将以type DetermineContractAddress类型为例进行更详细的介绍 —— 你可以从`DetermineContractAddress` 文档[11]中看到它需要 traitContractAddressFor。Contracts pallet 本身在contract::SimpleAddressDeterminator中实现了具有此特征的类型,因此我们可以使用该实现来满足我们的contracts::Trait。此时,我真的建议你不管是过程中出现问题或者想加深了解,都可以浏览下Contracts pallet[12]的源代码。

将合约添加到construct_runtime!Macro

接下来,我们需要将 pallet(模块)添加到construct_runtime!宏。. 为此,我们需要确定 pallet(模块)公开的类型,以便我们可以告诉运行时它们存在。可能的类型的完整列表可以在`construct_runtime!` macro documentation[13]中找到。

如果我们仔细查看 Contracts pallet,我们知道它具有:

ModuleStorage: Because it uses thedecl_storage!macro.ModuleEvents: Because it uses thedecl_event!macro.Callable Functions: Because it has dispatchable functions in thedecl_module!macro.Configuration Values: Because thedecl_storage!macro hasconfig()parameters.TheModuletype from thedecl_module!macro.

因此,当我们添加 pallet(模块)时,它将如下所示:

runtime/src/lib.rs

construct_runtime!(
pubenumRuntimewhere
Block=Block,
NodeBlock=opaque::Block,
UncheckedExtrinsic=UncheckedExtrinsic
{
/*--snip--*/

/***AddThisLine***/
Contracts:contracts::{Module,Call,Config,Storage,Event<T>},
}
);

请注意,并非所有的 pallet(模块)都会公开所有这些运行时类型,而有些可能会公开更多!你应该总是查看 pallet(模块)的源代码或 pallet(模块)的文档,以确定你需要公开其中哪种类型。

这是检查到目前为止运行时是否正确编译的另一个好时机。尽管运行时应进行编译,但整个节点还尚未进行编译。因此,我们仅使用此命令检查 runtime。

cargocheck-pnode-template-runtime

公开 Contracts API

某些 pallet(包括合同 Contracts pallet)公开了自定义 runtime API 和 RPC 端点。对于 Contracts pallet,这使得可以从链外读取合约状态。

不需要启用 Contracts pallet 上的 RPC 调用即可在我们的链中使用它。但是,我们将执行此操作以在不进行交易的情况下调用节点的存储。

我们首先在Cargo.toml中添加所需的 API 依赖项。

runtime/Cargo.toml

[dependencies.contracts-rpc-runtime-api]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
package = 'pallet-contracts-rpc-runtime-api'
version = '0.8.0-alpha.8'
tag = 'v2.0.0-alpha.8'

runtime/Cargo.toml

[features]
default = ["std"]
std = [
#--snip--
'contracts-rpc-runtime-api/std',
]

要获取合同变量的状态,我们必须调用 getter 函数,该函数将返回具有执行状态的ContractExecResult包装器。

我们需要将返回类型添加到运行时。将此添加到其他use语句中。

runtime/src/lib.rs

/***AddThisLine***/
usecontracts_rpc_runtime_api::ContractExecResult;
/*--snip--*/

现在,我们准备实现 contracts runtime API。在 runtime 快结束的时候,这发生在impl_runtime_apis!宏中。

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

/***AddThisBlock***/
implcontracts_rpc_runtime_api::ContractsApi<Block,AccountId,Balance,BlockNumber>
forRuntime
{
fncall(
origin:AccountId,
dest:AccountId,
value:Balance,
gas_limit:u64,
input_data:Vec<u8>,
)->ContractExecResult{
letexec_result=
Contracts::bare_call(origin,dest.into(),value,gas_limit,input_data);
matchexec_result{
Ok(v)=>ContractExecResult::Success{
status:v.status,
data:v.data,
},
Err(_)=>ContractExecResult::Error,
}
}

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

fnrent_projection(
address:AccountId,
)->contracts_primitives::RentProjectionResult<BlockNumber>{
Contracts::rent_projection(address)
}
}
/***EndAddedBlock***/
}

这是检查到目前为止运行时是否正确编译的另一个好时机。

cargocheck-pnode-template-runtime

更新外部节点

至此,我们已经完成了向 runtime 添加 pallet 的工作。现在,我们将注意力转向外部节点,该外部节点通常需要一些相应的更新。对于 Contracts pallet,我们将添加自定义 RPC 端点和创始配置。

添加 RPC 端点

With the proper runtime API exposed. We now add the RPC to the node's service to call into that runtime API. Because we are now working in the outer node, we are not building tono_stdand we don't have to maintain a dedicatedstdfeature.

公开了适当的 runtime API 后。现在,我们将 RPC 添加到节点的服务中,以调用该 runtime API。因为我们现在在外部节点上工作,所以我们没有在no_std上构建,也不必维护专用的std功能。

node/Cargo.toml

[dependencies]
#--snip--
jsonrpc-core = '14.0.5'

[dependencies.pallet-contracts-rpc]
git = 'https://github.com/paritytech/substrate.git'
version = '0.8.0-alpha.8'
tag = 'v2.0.0-alpha.8'

[dependencies.sc-rpc]
git = 'https://github.com/paritytech/substrate.git'
tag = 'v2.0.0-alpha.8'

node/src/service.rs

macro_rules!new_full_start{
($config:expr)=>{{
/***AddThisLine***/
usejsonrpc_core::IoHandler;

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


/* --snip-- */
Ok(import_queue)
})? // <- Remove semi-colon
/*** Add This Block ***/
.with_rpc_extensions(|builder| -> Result<IoHandler<sc_rpc::Metadata>, _> {
let handler = pallet_contracts_rpc::Contracts::new(builder.client().clone());
let delegate = pallet_contracts_rpc::ContractsApi::to_delegate(handler);

let mut io = IoHandler::default();
io.extend_with(delegate);
Ok(io)
})?;
/*** End Added Block ***/
(builder, import_setup, inherent_data_providers)
}}

初始配置

并不是所有的 pallet 都会有初始配置,但是如果你的 pallet 有的话,你可以从它的文档中进行学习,例如`pallet_contracts::GenesisConfig` 文档[14]就描述了在 Contracts pallet 中你需要描述的所有领域。

初始配置由node/src/chain_spec.rs控制。我们需要修改这个文件,来包含ContractsConfig类型,和顶部的合约价格单位。

node/src/chain_spec.rs

usenode_template_runtime::{ContractsConfig,ContractsSchedule};

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

重要说明:我们从函数参数中获取值_enable_println。确保删除参数定义之前的下划线。

fntestnet_genesis(initial_authorities:Vec<(AuraId,GrandpaId)>,
root_key:AccountId,
endowed_accounts:Vec<AccountId>,
_enable_println:bool)->GenesisConfig{

GenesisConfig{
/*--snip--*/

/***AddThisBlock***/
contracts:Some(ContractsConfig{
current_schedule:ContractsSchedule{
enable_println,
..Default::default()
},
}),
/***EndAddedBlock***/
}
}

运行升级后的链

现在,您可以编译并运行具有合约功能的节点了。使用发布模式编译节点

cargobuild--release

在运行链之前,我们首先需要清除链以删除旧的 runtime 逻辑,并为 Contracts pallet 初始化配置。可以在不清除链条的情况下升级链,但这不在本教程的范围之内。

./target/release/node-templatepurge-chain--dev
./target/release/node-template--dev

添加其他的 FRAME pallets

在本指南中,我们专门介绍了如何导入 Contracts pallet,但是,如本指南开头所述,每个 pallet(模块)都会有所不同。不用担心,你可以始终参考示范性 Substrate 节点 runtime[15],该 runtime 几乎包括 FRAME 中的每个 pallet(模块)。

在 Substrate 节点运行时的Cargo.toml文件中,你将看到一个如何导入每个不同 pallet(模块)的示例,在lib.rs文件中,你将找到如何将每个 pallet(模块)添加到你的 runtime。你基本上可以将在里面所做的复制到自己的 runtime。

了解更多

在自己的程序包中编写 runtime 的极简教程[16].现在,你的节点可以运行智能合约了,请继续学习Substrate ink! smart contracts[17].Substrate Recipes[18]提供了有关编写Runtime APIs[19]和Custom RPCs[20]的详细教程,例如本教程中探讨的内容。了解Chain Spec[21]文件以自定义您的 Genesis 配置。

参考文献

FRAME `Contracts` Pallet API[22]

参考链接

[1]

Substrate 节点模板:https://github.com/substrate-developer-hub/substrate-node-template

[2]

FRAME:https://www.substrate.io/kb/runtime/frame

[3]

Contracts pallet:https://docs.rs/crate/pallet-contracts/2.0.0-alpha.8

[4]

创建你的第一条 Substrate 链:https://www.substrate.io/tutorials/create-your-first-substrate-chain/v2.0.0-alpha.8

[5]

Substrate Node Template:https://github.com/substrate-developer-hub/substrate-node-template

[6]

正式文档:https://doc.rust-lang.org/cargo/reference/index.html

[7]

Balances pallet:https://docs.rs/crate/pallet-balances/2.0.0-alpha.8

[8]

std:https://doc.rust-lang.org/std/

[9]

no_std:https://rust-embedded.github.io/book/intro/no-std.html

[10]

contracts::Trait文档:https://docs.rs/pallet-contracts/2.0.0-alpha.8/pallet_contracts/trait.Trait.html

[11]

DetermineContractAddress文档:https://docs.rs/pallet-contracts/2.0.0-alpha.8/pallet_contracts/trait.Trait.html#associatedtype.DetermineContractAddress

[12]

Contracts pallet:https://github.com/paritytech/substrate/blob/v2.0.0-alpha.8/frame/contracts/src/lib.rs

[13]

construct_runtime!macro documentation:https://docs.rs/frame-support/2.0.0-alpha.8/frame_support/macro.construct_runtime.html

[14]

pallet_contracts::GenesisConfig文档:https://docs.rs/pallet-contracts/2.0.0-alpha.8/pallet_contracts/struct.GenesisConfig.html

[15]

示范性 Substrate 节点 runtime:https://github.com/paritytech/substrate/blob/v2.0.0-alpha.8/bin/node/runtime/

[16]

在自己的程序包中编写 runtime 的极简教程:https://www.substrate.io/tutorials/pallet-in-own-crate/v2.0.0-alpha.8

[17]

Substrate ink! smart contracts:https://www.substrate.io/kb/smart-contracts

[18]

Substrate Recipes:https://substrate.dev/recipes/

[19]

Runtime APIs:https://substrate.dev/recipes/3-entrees/runtime-api.html

[20]

Custom RPCs:https://substrate.dev/recipes/3-entrees/custom-rpc.html

[21]

Chain Spec:https://www.substrate.io/kb/integrate/chain-spec

[22]

FRAMEContractsPallet API:https://docs.rs/pallet-contracts/2.0.0-alpha.8/pallet_contracts/index.html



欢迎学习 Substrate:

https://substrate.dev/

关注 Substrate 进展:

https://github.com/paritytech/substrate

关注 Polkadot 进展:

https://github.com/paritytech/polkadot

申请 Bootcamp:

https://bootcamp.web3.foundation/

—-

编译者/作者:DotNews

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...