Skip to content

与GMP预编译交互

概览

Moonbeam路由流动性(MRL)是指Moonbeam作为端口平行链的用例,适用于从源链到其他波卡平行链的流动性。此用例因为通用消息传递(GMP)的出现而实现,其中带有任意数据和Token的消息可以通过与链无关的GMP协议在非平行链区块链上发送。这些GMP协议可以与波卡的XCM消息传递系统结合,以实现无缝流动性路由。

GMP预编译充当Moonbeam路由流动性的接口,作为来自GMP协议的承载Token消息和通过XCMP连接到Moonbeam的平行链之间的中间人。目前GMP预编译仅支持通过 Wormhole GMP协议传递流动性。

GMP预编译位于以下地址:

0x0000000000000000000000000000000000000816
0x0000000000000000000000000000000000000816
0x0000000000000000000000000000000000000816

实际上,开发者不太可能直接与预编译进行交互。GMP协议的中继层与预编译交互以完成跨链操作,因此跨链操作起源的源链是开发者有责任确保最终使用GMP预编译的地方。

GMP Solidity接口

Gmp.sol是一个允许开发者与预编译交互的Solidity接口:

  • wormholeTransferERC20(bytes memory vaa) - 接受一个Wormhole的桥接转账VAA (Verified Action Approval),通过Wormhole Token桥铸造Token并将流动性转移至自定义的有效负载multilocation。有效负载被预计称为预编译专属的SCALE编码项目,如先前在此教程的Wormhole部分解释一般

VAA为在源链交易后生成的包含有效负载的包,由Wormhole守护者网络间谍发现。

用户必须与预编译交互的最常见实例是在恢复的情况下,也就是中继器不完成MRL事务。举例来说,用户必须搜索其源链交易附带的VAA,然后手动调用wormholeTransferERC20函数。

为Wormhole构建有效负载

目前GMP预编译仅支持使用Wormhole通过Moonbeam发送流动性以及发送到其他平行链。GMP预编译不协助从平行链返回Moonbeam以及其他Wormhole连接链的路线。

要从像以太坊这样的与Wormhole连接的源链发送流动性,用户必须调用transferTokensWithPayload函数WormholeTokenBridge智能合约origin-chain部署。此函数需要一个字节有效负载,该有效负载必须格式化为包含在另一个预编译特定版本类型中的SCALE编码multilocation对象。

如果您不熟悉波卡生态系统,您可能不熟悉SCALE编码和multilocation。SCALE编码是波卡使用的一种紧凑形式的编码。MultiLocation类型用于定义波卡中的相对点,例如特定平行链上的特定账户(Polkadot区块链)。

Moonbeam的GMP协议需要一个multilocation来代表流动性路由的目的地,目的地可能代表着其他平行链上的账户。不论它是什么,这个目的地必须相对于Moonbeam来表达。

注意事项

Multilocation以相对形式出现是非常重要的,因为平行链团队有可能给你一个相对于他们自身链的multilocation,有可能与你认知不同。提供错误的multilocation有可能导致资金损失

每一个平行链有他们自己解读multilocation的函数,建议您与项目确认您所形成的multilocation是正确的,也就是说,一个最有可能通过账户形成的multilocation。

有很多种账户可以被包含在multilocation之中,您需要在行程您的multilocation之前了解相关信息。其中两种最常见的分别为:

  • AccountKey20 — 20个字节长的账户ID,包含如在Moonbeam中的以太坊兼容账户ID
  • AccountId32 — 32个字节长的账户ID,为波卡及平行链的标准

以下multilocation模板以其他平行链上的账户为目标,以Moonbeam作为相对来源。要使用它们,请将INSERT_PARACHAIN_ID替换为您希望向其发送资金的网络的平行链ID,并将INSERT_ADDRESS替换为您希望向该平行链发送资金的账户地址。

{
  parents: 1,
  interior: {
    X2: [
      { Parachain: 'INSERT_PARACHAIN_ID' },
      {
        AccountId32: {
          id: 'INSERT_ADDRESS',
        },
      },
    ],
  },
}
{
  parents: 1,
  interior: {
    X2: [
      { Parachain: 'INSERT_PARACHAIN_ID' },
      {
        AccountKey20: {
          key: 'INSERT_ADDRESS',
        },
      },
    ],
  },
}

如果没有正确的工具,可能很难对整个有效负载进行正确的SCALE编码,特别是因为预编译所需的自定义类型。幸运的是,有波卡JavaScript包可以帮助实现这一点。

这个预编译合约接受的user action有V1和V2两个版本。V1版本接受XcmRoutingUserAction类型,它会尝试将资产传送至multilocation定义的目标地址。V2版本接受XcmRoutingUserActionWithFee类型,它不仅会尝试将资产传送至目标地址,同时也接受费用的支付。中继节点能够使用V2版来定义处理交易所需要在Moonbeam支付的费用。

以下脚本展示了如何创建可用作GMP预编译有效负载的Uint8Array

import { ApiPromise, WsProvider } from '@polkadot/api';

enum MRLTypes {
  // Runtime defined MultiLocation. Allows for XCM versions 2 and 3
  XcmVersionedMultiLocation = 'XcmVersionedMultiLocation',
  // MRL payload (V1) that only defines the destination MultiLocation
  XcmRoutingUserAction = 'XcmRoutingUserAction',
  // Wrapper object for the MRL payload
  VersionedUserAction = 'VersionedUserAction',
}

// Parachain IDs of each parachain
enum Parachain {
  MoonbaseBeta = 888,
  // Insert additional parachain IDs
}

// List of parachains that use ethereum (20) accounts
const ETHEREUM_ACCOUNT_PARACHAINS = [Parachain.MoonbaseBeta];

// A function that creates a SCALE encoded payload to use with transferTokensWithPayload
async function createMRLPayload(
  parachainId: Parachain,
  account: string
): Promise<Uint8Array> {
  // Create a multilocation object based on the target parachain's account type
  const isEthereumStyle = ETHEREUM_ACCOUNT_PARACHAINS.includes(parachainId);
  const multilocation = {
    V3: {
      parents: 1,
      interior: {
        X2: [
          { Parachain: parachainId },
          isEthereumStyle
            ? { AccountKey20: { key: account } }
            : { AccountId32: { id: account } },
        ],
      },
    },
  };

  // Creates an API for Moonbeam that defines MRL's special types
  const wsProvider = new WsProvider('wss://wss.api.moonbase.moonbeam.network');
  const api = await ApiPromise.create({
    provider: wsProvider,
    types: {
      [MRLTypes.XcmRoutingUserAction]: {
        destination: MRLTypes.XcmVersionedMultiLocation,
      },
      [MRLTypes.VersionedUserAction]: {
        _enum: { V1: MRLTypes.XcmRoutingUserAction },
      },
    },
  });

  // Format multilocation object as a Polkadot.js type
  const versionedMultilocation = api.createType(
    MRLTypes.XcmVersionedMultiLocation,
    multilocation
  );
  const userAction = api.createType(MRLTypes.XcmRoutingUserAction, {
    destination: versionedMultilocation,
  });

  // Wrap and format the MultiLocation object into the precompile's input type
  const versionedUserAction = api.createType(MRLTypes.VersionedUserAction, {
    V1: userAction,
  });

  // SCALE encode resultant precompile formatted objects
  return versionedUserAction.toU8a();
}
import { ApiPromise, WsProvider } from '@polkadot/api';
import { u256 } from '@polkadot/types';

enum MRLTypes {
  // Runtime defined MultiLocation. Allows for XCM versions 2 and 3
  XcmVersionedMultiLocation = 'XcmVersionedMultiLocation',
  // MRL payload (V2) that defines the destination MultiLocation and a 
  // fee for the relayer
  XcmRoutingUserActionWithFee = 'XcmRoutingUserActionWithFee',
  // Wrapper object for the MRL payload
  VersionedUserAction = 'VersionedUserAction',
}

// Parachain IDs of each parachain
enum Parachain {
  MoonbaseBeta = 888,
  // Insert additional parachain IDs
}

// List of parachains that use ethereum (20) accounts
const ETHEREUM_ACCOUNT_PARACHAINS = [Parachain.MoonbaseBeta];

// A function that creates a SCALE encoded payload to use with 
// transferTokensWithPayload
async function createMRLPayload(
  parachainId: Parachain,
  account: string,
  fee: u256
): Promise<Uint8Array> {
  // Create a multilocation object based on the target parachain's account
  // type
  const isEthereumStyle = ETHEREUM_ACCOUNT_PARACHAINS.includes(parachainId);
  const multilocation = {
    V3: {
      parents: 1,
      interior: {
        X2: [
          { Parachain: parachainId },
          isEthereumStyle
            ? { AccountKey20: { key: account } }
            : { AccountId32: { id: account } },
        ],
      },
    },
  };

  // Creates an API for Moonbeam that defines MRL's special types
  const wsProvider = new WsProvider('wss://wss.api.moonbase.moonbeam.network');
  const api = await ApiPromise.create({
    provider: wsProvider,
    types: {
      [MRLTypes.XcmRoutingUserActionWithFee]: {
        destination: MRLTypes.XcmVersionedMultiLocation,
        fee: 'U256',
      },
      [MRLTypes.VersionedUserAction]: {
        _enum: { V2: MRLTypes.XcmRoutingUserActionWithFee },
      },
    },
  });

  // Format multilocation object as a Polkadot.js type
  const versionedMultilocation = api.createType(
    MRLTypes.XcmVersionedMultiLocation,
    multilocation
  );
  const userAction = api.createType(MRLTypes.XcmRoutingUserActionWithFee, {
    destination: versionedMultilocation,
    fee,
  });

  // Wrap and format the MultiLocation object into the precompile's input type
  const versionedUserAction = api.createType(MRLTypes.VersionedUserAction, {
    V2: userAction,
  });

  // SCALE encode resultant precompile formatted objects
  return versionedUserAction.toU8a();
}

限制

GMP预编译仍然在其发展的早期阶段,目前还是有许多限制,当前仅支持至平行链“满意路径”。以下为您需要了解的限制内容:

  • 目前没有费用机制。在Moonbeam上运行将流动性转移至其他平行链的中继层将会负责支付交易费用,此机制或将在未来更动
  • 预编译并不会确保目标链是否支持传送的Token。错误的multilocation有可能导致资金损失
  • 错误的构建multilocation将导致回溯,Token将会被锁住并导致资金损失
  • 目前并没有从平行链至其他如以太坊的链的返回路径。这会是一个在一键方案实现前需要研究的协议层级课题
    • 由于ERC-20 XC资产的限制,通过Moonbeam从平行链发送回Token的唯一方法是在原始平行链上拥有xcGLMR,并在发送回Token时将其用作费用
Last update: February 1, 2024
| Created: August 2, 2023