diff --git a/.snippets/code/precompiles/gmp/v1-payload.ts b/.snippets/code/precompiles/gmp/v1-payload.ts new file mode 100644 index 000000000..36b1d59ca --- /dev/null +++ b/.snippets/code/precompiles/gmp/v1-payload.ts @@ -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 { + // 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(); +} diff --git a/.snippets/code/precompiles/gmp/v2-payload.ts b/.snippets/code/precompiles/gmp/v2-payload.ts new file mode 100644 index 000000000..e742ee8c5 --- /dev/null +++ b/.snippets/code/precompiles/gmp/v2-payload.ts @@ -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 { + // 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(); +} diff --git a/builders/pallets-precompiles/precompiles/gmp.md b/builders/pallets-precompiles/precompiles/gmp.md index 6773195bb..895aa23ed 100644 --- a/builders/pallets-precompiles/precompiles/gmp.md +++ b/builders/pallets-precompiles/precompiles/gmp.md @@ -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). @@ -68,88 +68,57 @@ 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 } @@ -157,6 +126,6 @@ The GMP precompile is currently in its early stages. There are many restrictions - 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