Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .snippets/code/precompiles/gmp/v1-payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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();
}
79 changes: 79 additions & 0 deletions .snippets/code/precompiles/gmp/v2-payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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();
}
109 changes: 39 additions & 70 deletions builders/pallets-precompiles/precompiles/gmp.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The most common instance that a user will have to interact with the precompile i

Currently the GMP precompile only supports sending liquidity with Wormhole, through Moonbeam, and into other parachains. The GMP precompile does not assist with a route from parachains back to Moonbeam and subsequently Wormhole connected chains.

To send liquidity from a Wormhole-connected origin chain like Ethereum, users must invoke the [`transferTokensWithPayload` method](https://book.wormhole.com/technical/evm/tokenLayer.html#contract-controlled-transfer){target=_blank} on the [origin-chain's deployment](https://book.wormhole.com/reference/contracts.html#token-bridge){target=_blank} of the [WormholeTokenBridge smart contract](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/bridge/interfaces/ITokenBridge.sol){target=_blank}. This function requires a bytes payload, which must be formatted as a SCALE encoded multilocation object wrapped within [another precompile-specific versioned type](https://github.com/moonbeam-foundation/moonbeam/blob/runtime-2400/precompiles/gmp/src/types.rs#L25-L39){target=_blank}.
To send liquidity from a Wormhole-connected origin chain like Ethereum, users must invoke the [`transferTokensWithPayload` method](https://book.wormhole.com/technical/evm/tokenLayer.html#contract-controlled-transfer){target=_blank} on the [origin-chain's deployment](https://book.wormhole.com/reference/contracts.html#token-bridge){target=_blank} of the [WormholeTokenBridge smart contract](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/bridge/interfaces/ITokenBridge.sol){target=_blank}. This function requires a bytes payload, which must be formatted as a SCALE encoded multilocation object wrapped within [another precompile-specific versioned type](https://github.com/moonbeam-foundation/moonbeam/blob/{{ networks.moonbase.spec_version }}/precompiles/gmp/src/types.rs#L25-L48){target=_blank}.

You may be unfamiliar with both SCALE encoding and multilocations if you are not familiar with the Polkadot ecosystem. [SCALE encoding](https://docs.substrate.io/reference/scale-codec/){target=_blank} is a compact form of encoding that Polkadot uses. The [`MultiLocation` type](https://wiki.polkadot.network/docs/learn-xcvm){target=_blank} is used to define a relative point in Polkadot, such as a specific account on a specific parachain (Polkadot blockchain).

Expand All @@ -68,95 +68,64 @@ The following multilocation templates target accounts on other parachains with M

=== "AccountId32"

```json
```js
{
"parents": 1,
"interior": {
"X2": [
{ "Parachain": "INSERT_PARACHAIN_ID" },
{
"AccountId32": {
"id": "INSERT_ADDRESS"
}
}
]
}
parents: 1,
interior: {
X2: [
{ Parachain: 'INSERT_PARACHAIN_ID' },
{
AccountId32: {
id: 'INSERT_ADDRESS',
},
},
],
},
}
```

=== "AccountKey20"

```json
```js
{
"parents": 1,
"interior": {
"X2": [
{ "Parachain": "INSERT_PARACHAIN_ID" },
{
"AccountKey20": {
"key": "INSERT_ADDRESS"
}
}
]
}
}
```

It can be difficult to correctly SCALE encode the entire payload without the right tools, especially due to the [custom types expected by the precompile](https://github.com/moonbeam-foundation/moonbeam/blob/runtime-2400/precompiles/gmp/src/types.rs#L25-L39){target=_blank}. Fortunately, there are Polkadot JavaScript packages that can assist with this, such as [`@polkadot/types`](https://www.npmjs.com/package/@polkadot/types){target=_blank}. The following script shows how to create a `Uint8Array` that can be used as a payload for the GMP precompile:

```javascript
import { TypeRegistry, Enum, Struct } from '@polkadot/types';

// Creates a type registry to properly work with the precompile's input types
const registry = new TypeRegistry();

// Define the precompile's input types VersionedUserAction and XcmRoutingUserAction
class VersionedUserAction extends Enum {
constructor(value) {
super(registry, { V1: XcmRoutingUserAction }, value);
}
}
class XcmRoutingUserAction extends Struct {
constructor(value) {
super(registry, { destination: 'VersionedMultiLocation' }, value);
}
}

// A function that creates a SCALE encoded payload to use with transferTokensWithPayload
function createMRLPayload(parachainId, account, isEthereumStyle) {
// Create a multilocation object based on the target parachain's account type
const versionedMultiLocation = {
v1: {
parents: 1,
interior: {
X2: [
{ Parachain: parachainId },
isEthereumStyle ?
{ AccountKey20: { key: account } } :
{ AccountId32: { id: account }
}]
}
{ Parachain: 'INSERT_PARACHAIN_ID' },
{
AccountKey20: {
key: 'INSERT_ADDRESS',
},
},
],
},
}
};
```

// Format multilocation object as a Polkadot.js type
const destination = registry.createType('VersionedMultiLocation', versionedMultiLocation);
It can be difficult to correctly SCALE encode the entire payload without the right tools, especially due to the [custom types expected by the precompile](https://github.com/moonbeam-foundation/moonbeam/blob/{{ networks.moonbase.spec_version }}/precompiles/gmp/src/types.rs#L25-L48){target=_blank}. Fortunately, the Polkadot.js API can assist with this.

// Wrap and format the multiLocation object into the precompile's input type
const userAction = new XcmRoutingUserAction({ destination });
const versionedUserAction = new VersionedUserAction({ V1: userAction });
The versioned user action expected by the precompile accepts two versions: V1 and V2. V1 accepts the `XcmRoutingUserAction` type, which attempts to route the transferred assets to the destination defined by the multilocation. V2 accepts the `XcmRoutingUserActionWithFee` type, which also attempts to route the transferred assets to the destination but also allows a fee to be paid. Relayers can use V2 to specify a fee to charge on Moonbeam to relay the transaction to the given destination.

// SCALE encode resultant precompile formatted objects
return versionedUserAction.toU8a();
}
```
The following script shows how to create a `Uint8Array` that can be used as a payload for the GMP precompile:

=== "V1"

```typescript
--8<-- 'code/precompiles/gmp/v1-payload.ts'
```

=== "V2"

```typescript
--8<-- 'code/precompiles/gmp/v2-payload.ts'
```

## Restrictions {: #restrictions }

The GMP precompile is currently in its early stages. There are many restrictions, and it only supports a “happy path” into parachains. Here are some restrictions that you should be aware of:

- There is currently no fee mechanism. Relayers that run the forwarding of liquidity on Moonbeam to a parachain will be subsidizing transactions. This may change in the future
- The precompile does not check to ensure that the destination chain supports the token that is being sent to it. **Incorrect multilocations may result in loss of funds**
- Errors in constructing a multilocation will result in reverts, which will trap tokens and a loss of funds
- Errors in constructing a multilocation will result in reverts, which will trap tokens and result in a loss of funds
- There is currently no recommended path backwards, from parachains to other chains like Ethereum. There is additional protocol level work that must be done before a one-click method can be realized
- Due to a restriction with the ERC-20 XC-assets, the only way to send tokens from a parachain back through Moonbeam is to have xcGLMR on the origin parachain and use it as a fee asset when sending tokens back