使用XCM Transactor Pallet进行远程执行¶
概览¶
XCM消息是由跨共识虚拟机(XCVM)执行的一系列指令组成。这些指令的组合会产生预先确定的操作,例如跨链Token转移,更有趣的是,远程跨链执行。
然而,从头开始构建XCM消息还是比较困难。此外,XCM消息从根账户(即SUDO或通过民主投票)发送给生态系统中的其他参与者,这对于希望通过简单交易实现远程跨链调用的项目来说并不合适。
要克服这些困难,开发者可以利用wrapper函数或pallet来使用波卡或Kusama上的XCM功能,例如XCM Transactor Pallet。另外,XCM Transactor Pallet允许用户从主权账户衍生出来的账户(称为衍生账户)执行远程跨链调用,从而可通过简单的交易轻松执行。
pallet的两个主要extrinsic是通过主权衍生账户或从给定multilocation计算的衍生账户进行交易。每个extrinsic都相应命名。
本教程将向您展示如何使用XCM Transactor Pallet在生态系统(中继链或平行链)中从基于Moonbeam的网络发送XCM消息至其他链。此外,您还将学习到如何使用XCM Transactor预编译通过以太坊API执行同样的操作。
请注意,通过XCM消息进行远程执行仍然有一些限制。
开发者须知悉,若发送不正确的XCM消息可能会导致资金丢失。因此,XCM功能需先在测试网上进行测试后才可移至生产环境。
用于远程执行的XCM指令¶
通过XCM进行远程执行的相关指令,有但不限于:
DescendOrigin
- 在目标链中执行。改变将用于执行后续XCM指令的起始地址WithdrawAsset
- 在目标链中执行。移除资产并将其放于待使用BuyExecution
- 在目标链中执行。从持有资产中提取用于支付执行费用。支付的费用取决于目标链Transact
- 在目标链中执行。从给定原始链派遣编码的调用数据
当由XCM Transactor Pallet创建的XCM消息执行后,必须支付费用。所有的相关信息可以在XCM费用页面的XCM Transactor费用部分找到。
相关XCM定义¶
- 主权账户 — 生态系统中每条链预设的账户,用于中继链和其他平行链。它的地址为特定单词和平行链ID连接的
blake2
哈希(中继链中的主权账户为blake2(para+ParachainID)
,其他中的主权账户为blake2(sibl+ParachainID)
平行链),将哈希截断为正确的长度。该帐户归root所支配,只能通过SUDO(如果有)或民主(公投)使用。主权账户通常在其它链上签署 XCM 消息 -
Multilocation — 一种指定整个中继链或平行链生态系统中来源点(相对或绝对来源)的方式;例如,它可用于指定特定的平行链、资产、账户,甚至是一个Pallet。一般而言,multilocation定义包含为
parents
和interior
:parents
- 是指需要从来源点“跳跃”多少次可以进入parent
区块链interior
,是指定义目标点需要多少个字段
例如,要从另一个平行链中定位ID为
1000
的平行链,multilocation将是{ "parents": 1, "interior": { "X1": [{ "Parachain": 1000 }]}}
-
Multilocation-derivative account — 这会生产一个从Descend Origin XCM指令和提供的mulitlocation设置的新来源衍生的无私钥账户。对于基于Moonbeam的网络,衍生方法是计算multilocation的
blake2
哈希,包括原始平行链ID并将哈希截断为正确的长度(以太坊格式的账户为20个字节)。Transact
指令执行时会发生XCM调用原始转换。因此,每个平行链可以使用自己想要的程序转换起点,从而发起交易的用户可能在每条平行链上拥有不同的衍生账户。该衍生账户支付交易费用,并设置为调用的派遣员 - Transact information — 与XCM Transactor extrinsic的XCM远程执行部分的额外权重和费用信息相关。这是必要的,因为XCM交易费用由主权账户进行支付。因此,XCM Transactor计算此费用,并向XCM Transactor extrinsic的发送者收取对应XC-20 token的预估费用来偿还主权账户
XCM Transactor Pallet接口¶
Extrinsics¶
XCM Transactor Pallet提供以下extrinsics(函数):
- hrmpManage(action, fee, weightInfo) - 管理与打开、接受和关闭HRMP通道相关的HRMP操作。给定的操作可以是以下三个操作中的任何一个:
InitOpen
、Accept
、Close
、和Cancel
- removeFeePerSecond(assetLocation) — 移除其储备链中给定资产的每秒费用信息。资产定义为multilocation
- removeTransactInfo(location) — 移除给定链的交易信息,定义为multilocation
- setFeePerSecond(assetLocation, feePerSecond) — 设置其储备链中给定资产的每秒交易费信息。资产定义为multilocation。
feePerSecond
是每秒XCM执行的Token单位,将会向XCM Transactor extrinsic的发送者收取费用 - setTransactInfo(location, transactExtraWeight, maxWeight) — 设置给定链的交易信息,定义为multilocation。交易信息包含:
- transactExtraWeight — 支付XCM指令执行费用(
WithdrawAsset
、BuyExecution
和Transact
)的权重,预计至少比移除XCM指令执行使用的费用高出10%以上 - maxWeight — 允许远程XCM执行的最大权重单位
- transactExtraWeightSigned — (可选)支付XCM指令执行费用(
DescendOrigin
、WithdrawAsset
、BuyExecution
和Transact
)的权重,预计至少比移除XCM指令执行使用的费用高出10%以上
- transactExtraWeight — 支付XCM指令执行费用(
- transactThroughSigned(destination, fee, call, weightInfo) — 发送XCM消息,包含在给定目标链上远程执行特定调用的指令。远程调用将通过目标平行链衍生的新账户签署和执行。对于基于Moonbeam的网络,此账户是继承的multilocation的
blake2
哈希,截断成正确的长度。XCM Transactor Pallet计算远程执行的费用,并向extrinsic的发送者收取资产ID给出的对应XC-20 token预估费用 - transactThroughSovereign(destination, feePayer, fee, call, originKind, weightInfo) — 发送XCM消息,包含在给定目标链上远程执行特定调用的指令。程调用将通过支付费用的原始平行链主权账户签署,而交易是从给定起始账户发送。XCM Transactor Pallet计算远程执行的费用,并通过资产multilocation向给定账户收取对应XC-20 token的预估费用
其中需要输入的内容如下:
- assetLocation — 代表储备链上资产的multilocation,用于设置或获取每秒交易信息的费用
- location — 代表生态系统中一条链的multilocation,用于设置或获取交易信息
- destination — 代表生态系统中一条链的multilocation,XCM消息将发送到该位置
-
fee — 一个枚举(enum),为开发者提供两个关于如何定义XCM执行费用项目的选项。两种选项均依赖于
feeAmount
,即您为执行发送的XCM消息而提供的每秒XCM执行的资产单位。设置费用项目的两种不同方式如下:- AsCurrencyID — 用于支付远程调用执行的币种ID。不同的runtime有不同的定义ID的方式。以基于Moonbeam网络为例,
SelfReserve
指原生Token,ForeignAsset
指XC-20资产ID(区别于XC-20地址)
对于基于Moonbeam的网络,
SelfReserve
指的是原生Token,ForeignAsset
指的是外部 XC-20的资产ID {target=_blank}(不要与XC-20地址混淆),而Erc20
指的是原生XC-20- AsMultiLocation — 代表用于执行XCM时支付费用的资产multilocation
- innerCall — 在目标链中执行的调用的编码调用数据。如果通过主权衍生账户进行交易,这将包装在
asDerivative
选项中 - weightInfo — 包含所有权重相关信息的结构。若没有提供足够的权重,则XCM执行将失败,资金可能会被锁定在主权账户或特定pallet中。因此,正确设置目标权重以避免XCM执行失败至关重要。结构包含以下两种字段:
- transactRequiredWeightAtMost — 与
Transact
调用本身执行相关的权重。对于通过主权衍生的交易,您也需要考虑asDerivative
extrinsic的权重。但是,这不会包含在所有XCM指令的成本(权重)当中 - overallWeight — XCM Transactor extrinsic可以使用的所有权重。这包含所有XCM指令以及调用本身(transactRequiredWeightAtMost)的权重
- call — 类似于
innerCall
,但是并没有用asDerivative
extrinsic包装 - feePayer — 将通过主权账户支付远程XCM执行交易费用的地址。费用将根据对应的XC-20 token收取
- originKind — 在目标链中远程调用的派遣者。目前有四种派遣者类型可使用
- AsCurrencyID — 用于支付远程调用执行的币种ID。不同的runtime有不同的定义ID的方式。以基于Moonbeam网络为例,
存储方法¶
XCM Transactor Pallet包含以下只读存储方法:
- destinationAssetFeePerSecond() — 返回给定multilocation资产的每秒费用。这能够将权重转换成费用。如果
feeAmount
设置为None
,pallet extrinsics将读取存储元素 - palletVersion() — 从存储库返回当前pallet的版本
- transactInfoWithWeightLimit(location) — 返回给定multilocation的交易信息。如果
feeAmount
设置为None
,pallet extrinsics将读取存储元素
Pallet常量¶
XCM Transactor Pallet包含以下只读函数以获取pallet常量:
- baseXcmWeight() — 返回每个XCM指令执行所需的基本XCM权重
- selfLocation() — 返回链的multilocation
通过签署函数进行XCM Transactor交易¶
此部分包含使用transactThroughSigned
函数通过XCM Transactor Pallet为远程执行构建XCM消息。但是,由于目标平行链暂未公开,您将无法跟进。
注意事项
请确保您已在目标链中允许将要远程执行的调用!
查看先决条件¶
要在发送extrinsics,您需要准备以下内容:
- 在原始链上的账户拥有一定资金
- 资金所在的目标链上的multilocation衍生账户。您可以通过使用
calculate-multilocation-derivative-account.ts
脚本计算该地址
在本示例中,使用的账户如下:
- Alice在原始平行链中的地址为
0x44236223aB4291b93EEd10E4B511B37a398DEE55
- 在目标平行链中的multilocation衍生账户地址为
0x5c27c4bb7047083420eddff9cddac4a0a120b45c
2
构建XCM¶
由于您将与XCM Transactor Pallet的transactThroughSigned
函数交互,您需要组装dest
、fee
、call
和weightInfo
参数。为此,您可以执行以下步骤:
-
定义目标multilocation,其目标是平行链888:
const dest = { V3: { parents: 1, interior: { X1: { Parachain: 888 } }, }, };
-
定义
fee
信息,其将要求您:- 定义币种ID并提供资产详情
- 设置费用金额
const fee = { currency: { AsCurrencyId: { ForeignAsset: 3547752324713722007834302681851459189n }, }, feeAmount: 50000000000000000n, };
const fee = { currency: { AsCurrencyId: { Erc20: { contractAddress: ERC_20_ADDRESS} }, }, feeAmount: 50000000000000000n, };
-
定义将在目标链中执行的
call
。这里需要pallet、函数和输入值的编码调用数据它可以在Polkadot.js Apps中构建(必须连接至目标链)或使用Polkadot.js API。对于本示例而言,内部调用是将目标链的1个Token余额简单转移到Alice的账户:
const call = '0x030044236223ab4291b93eed10e4b511b37a398dee5513000064a7b3b6e00d';
-
设置
weightInfo
,其包含所需的transactRequiredWeightAtMost
权重和可选的overallWeight
参数。两个权重参数都要求您指定refTime
和proofSize
,其中refTime
是可用于执行的计算时间量,proofSize
是可使用的存储量(以字节为单位)。对于每个参数,您可以遵循以下准则:- 对于
transactRequiredAtMost
,该值必须包含asDerivative
extrinsic。然而,这并不包含XCM指令的权重。在本示例中,将refTime
设置为1000000000
的权重单位,将proofSize
设置为40000
- 对于
overallWeight
,该值必须是transactRequiredWeightAtMost加上在目标链中XCM指令执行成本所需的权重之和。如果您不提供此值,pallet将使用存储中的元素(若有)。在本示例中,将refTime
设置为2000000000
权重单位,将将proofSize
设置为50000
const weightInfo = { transactRequiredWeightAtMost: { refTime: 1000000000n, proofSize: 40000n }, overallWeight: { refTime: 2000000000n, proofSize: 50000n }, };
Note
关于
refTime
和proofSize
数值的准确预估,您可以使用在远程EVM调用教程中描述的Polkadot.js API的paymentInfo
函数。 - 对于
现在,您已经有了每个参数的值,您可以为交易编写脚本了。为此,您可以执行以下步骤:
-
提供调用的输入数据,这包含:
- 用于创建提供商的Moonbase Alpha端点URL
transactThroughSigned
函数的每个参数的值
-
创建一个用于发送交易的Keyring实例
- 创建Polkadot.js API提供商
- 使用
dest
、fee
、call
和weightInfo
值制作xcmTransactor.transactThroughSigned
extrinsic - 使用
signAndSend
extrinsic和在第二个步骤创建的Keyring实例发送交易
请记住
本教程的操作仅用于演示目的,请勿将您的私钥存储至JavaScript文档中。
import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'; // Version 9.13.6
// 1. Provide input data
const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';
const privateKey = 'INSERT_PRIVATE_KEY';
const dest = {
V3: {
parents: 1,
interior: { X1: { Parachain: 888 } },
},
};
const fee = {
currency: {
AsCurrencyId: { ForeignAsset: 35487752324713722007834302681851459189n },
},
feeAmount: 50000000000000000n,
};
const call = '0x030044236223ab4291b93eed10e4b511b37a398dee5513000064a7b3b6e00d';
const weightInfo = {
transactRequiredWeightAtMost: { refTime: 1000000000n, proofSize: 40000n },
overallWeight: { refTime: 2000000000n, proofSize: 50000n },
};
// 2. Create Keyring instance
const keyring = new Keyring({ type: 'ethereum' });
const alice = keyring.addFromUri(privateKey);
const transactThroughSigned = async () => {
// 3. Create Substrate API provider
const substrateProvider = new WsProvider(providerWsURL);
const api = await ApiPromise.create({ provider: substrateProvider });
// 4. Craft the extrinsic
const tx = api.tx.xcmTransactor.transactThroughSigned(
dest,
fee,
call,
weightInfo
);
// 5. Send the transaction
const txHash = await tx.signAndSend(alice);
console.log(`Submitted with hash ${txHash}`);
api.disconnect();
};
transactThroughSigned();
注意事项
您可以使用以下编码的调用数据在Polkadot.js Apps上查看上述脚本的示例,该脚本将1个xcUNIT发送给中继链上Alice的账户:0x210603010100e10d00017576e5e612ff054915d426c546b1b21a010000c52ebca2b10000000000000000007c030044236223ab4291b93eed10e4b511b37a398dee5513000064a7b3b6e00d02286bee02710200010300943577420d0300
。
交易处理后,Alice应该在目标链的地址上收到1个Token。
XCM Transactor预编译¶
XCM Transactor预编译合约允许开发者通过基于Moonbeam网络的以太坊API访问XCM Transactor Pallet功能。与其他预编译合约相似,XCM Transactor预编译位于以下地址:
{{ no such element: dict object['xcm_transactor'] }}
{{ no such element: dict object['xcm_transactor'] }}
{{ no such element: dict object['xcm_transactor'] }}
XCM Transactor旧版预编译仍可在所有基于Moonbeam网络中使用。但是,旧版本将在不久的将来被弃用,因此所有实现都必须迁移到较新的接口。XCM Transactor旧版预编译位于以下地址:
{{ no such element: dict object['xcm_transactor_legacy'] }}
{{ no such element: dict object['xcm_transactor_legacy'] }}
{{ no such element: dict object['xcm_transactor_legacy'] }}
注意事项
在Moonbeam使用预编译合约时,可能会出现一些意想不到的后果。 请参阅安全注意事项 页面了解更多信息。
XCM Transactor Solidity接口¶
XcmTransactor.sol是一个接口,开发者可以用其通过以太坊API与XCM Transactor Pallet进行交互。
注意事项
XCM Transactor预编译的旧版本将在不久的将来被弃用,因此所有实现都必须迁移到较新的接口。
此接口包含以下函数:
- indexToAccount(uint16 index) — 只读函数,返回授权使用给定索引的基于Moonbeam网络主权账户操作的注册地址
- transactInfoWithSigned(Multilocation memory multilocation) — 只读函数,对于定义为multilocation的给定链,返回考虑与外部调用执行(
transactExtraWeight
)关联的3个XCM指令的交易消息。这也将返回通过签署extrinsic(transactExtraWeightSigned
)交易的DescendOrigin
XCM指令关联的额外权重信息 - feePerSecond(Multilocation memory multilocation) — 只读函数,对于作为multilocation的给定资产,返回每秒XCM执行的Token单位,其作为XCM执行费用收取。这对于给定链有多种资产可以作为手续费进行支付使非常有用
- transactThroughSignedMultilocation(Multilocation memory dest, Multilocation memory fee_location, uint64 transactRequiredWeightAtMost, bytes memory call, uint256 feeAmount, uint64 overallWeight) — 表示上述示例中描述的
transactThroughSigned
方法的函数,将fee类型设置为AsMultiLocation。您需要提供Token的资产multilocation来支付费用,而不是XC-20 Tokenaddress
- transactThroughSigned(Multilocation memory dest, address fee_location_address, uint64 transactRequiredWeightAtMost, bytes memory call, uint256 feeAmount, uint64 overallWeight) — 表示上述示例中描述的
transactThroughSigned
方法的函数,将fee类型设置为AsCurrencyId。您将需要提供用于支付费用的Token的资产XC-20地址,而不是资产ID - encodeUtilityAsDerivative(uint8 transactor, uint16 index, bytes memory innerCall) - 给定要使用的交易者(transactor)、衍生账户的索引(index)以及要从衍生地址执行的内部调用(innerCall),对
asDerivative
包装调用进行编码
构建预编译Multilocation¶
在XCM Transactor预编译接口中,Multilocation
结构定义为如下:
struct Multilocation {
uint8 parents;
bytes[] interior;
}
请注意每个multilocation皆有parents
元素,请使用uint8
和一组字节定义。Parents代表有多少“hops”在通过中继链中您需要执行的传递向上方向。作为uint8
,您将会看到以下数值:
Origin | Destination | Parents Value |
---|---|---|
Parachain A | Parachain A | 0 |
Parachain A | Relay Chain | 1 |
Parachain A | Parachain B | 1 |
字节阵列(bytes[]
)定义了内部参数以及其在multilocation的内容。阵列的大小则根据以下定义interior
数值:
Array | Size | Interior Value |
---|---|---|
[] | 0 | Here |
[XYZ] | 1 | X1 |
[XYZ, ABC] | 2 | X2 |
[XYZ, ... N] | N | XN |
注意事项
内部值Here
通常用于中继链(作为目标链或以中继链资产为目标)。
假设所有字节阵列包含数据。所有元素的首个字节(2个十六进制数值)与XN
部分的选择器相关,举例来说:
Byte Value | Selector | Data Type |
---|---|---|
0x00 | Parachain | bytes4 |
0x01 | AccountId32 | bytes32 |
0x02 | AccountIndex64 | u64 |
0x03 | AccountKey20 | bytes20 |
0x04 | PalletInstance | byte |
0x05 | GeneralIndex | u128 |
0x06 | GeneralKey | bytes[] |
接着,根据选择器及其数据类型,以下字节对应于提供的实际数据。请注意在Polkadot.js Apps示例中出现的AccountId32
,AccountIndex64
和AccountKey20
,network
将会在最后添加。如下所示:
Selector | Data Value | Represents |
---|---|---|
Parachain | "0x00+000007E7" | Parachain ID 2023 |
AccountId32 | "0x01+AccountId32+00" | AccountId32, Network(Option) Null |
AccountId32 | "0x01+AccountId32+02" | AccountId32, Network Polkadot |
AccountKey20 | "0x03+AccountKey20+00" | AccountKey20, Network(Option) Null |
PalletInstance | "0x04+03" | Pallet Instance 3 |
注意事项
interior
数据通常需要使用引号包含。如果您未遵循此规则,您将会获得invalid tuple value
错误。
下面的代码片段介绍了Multilocation
结构的一些示例,他们需要被输入到XCM Transactor预编译函数中:
// Multilocation targeting the relay chain asset from a parachain
{
1, // parents = 1
[] // interior = here
}
// Multilocation targeting Moonbase Alpha DEV token from another parachain
{
1, // parents = 1
// Size of array is 2, meaning is an X2 interior
[
"0x00000003E8", // Selector Parachain, ID = 1000 (Moonbase Alpha)
"0x0403" // Pallet Instance = 3
]
}
// Multilocation targeting aUSD asset on Acala
{
1, // parents = 1
// Size of array is 1, meaning is an X1 interior
[
"0x00000007D0", // Selector Parachain, ID = 2000 (Acala)
"0x060001" // General Key Selector + Asset Key
]
}
| Created: July 8, 2022