From 5868dfe78a1efcfc74f3cff8304772907306381c Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Mon, 20 Mar 2023 16:57:38 -0300 Subject: [PATCH 01/39] feat: add reserve transfer example --- examples/xcm_reserve_transfer.ts | 51 +++++++++++++++++ zombienets/xcm_playground.toml | 94 ++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 examples/xcm_reserve_transfer.ts create mode 100644 zombienets/xcm_playground.toml diff --git a/examples/xcm_reserve_transfer.ts b/examples/xcm_reserve_transfer.ts new file mode 100644 index 000000000..0be2d2905 --- /dev/null +++ b/examples/xcm_reserve_transfer.ts @@ -0,0 +1,51 @@ +import { alice } from "capi" +import { + ParasSudoWrapper, + Sudo, + System, + types as relayTypes, +} from "zombienet/xcm_playground.toml/alice/@latest/mod.js" +import { + Assets, + // types as statemineTypes, +} from "zombienet/xcm_playground.toml/statemine-collator01/@latest/mod.js" +// import { +// System, +// types, +// XcmPallet, +// } from "zombienet/xcm_playground.toml/trappist-collator01/@latest/mod.js" + +const result = await System.Account.value(alice.publicKey).run() + +console.log(result) + +function setup() { + const forceCreateAsset = Assets.forceCreate({ + id: 1, + isSufficient: true, + minBalance: 1n, + owner: alice.address, + }) + + const createAsset = Sudo.sudo({ + call: ParasSudoWrapper.sudoQueueDownwardXcm({ + id: 1000, + xcm: { + type: "V2", + value: [ // convert to Array + relayTypes.xcm.v2.Instruction.Transact({ + originType: "Superuser", + requireWeightAtMost: 1000000000n, + call: forceCreateAsset, // convert to t.xcm.double_encoded.DoubleEncoded + }), + ], + }, + }), + }) + + return createAsset +} + +if (import.meta.main) { + await setup().run() +} diff --git a/zombienets/xcm_playground.toml b/zombienets/xcm_playground.toml new file mode 100644 index 000000000..88e0c2c4b --- /dev/null +++ b/zombienets/xcm_playground.toml @@ -0,0 +1,94 @@ +[settings] +timeout = 1000 + +[relaychain] +default_image = "docker.io/paritypr/polkadot-debug:master" +default_command = "polkadot" +chain = "rococo-local" + +[[relaychain.nodes]] +name = "alice" +validator = true +extra_args = ["-lparachain=debug"] + +[[relaychain.nodes]] +name = "bob" +validator = true +extra_args = ["-lparachain=debug"] + +[[relaychain.nodes]] +name = "charlie" +validator = true +extra_args = ["-lparachain=debug"] + +[[parachains]] +id = 1000 +cumulus_based = true +chain = "statemine-local" + +[[parachains.collators]] +name = "statemine-collator01" +image = "docker.io/parity/polkadot-parachain:latest" +command = "polkadot-parachain" +args = ["--log=xcm=trace,pallet-assets=trace"] + +[[parachains]] +id = 2000 +cumulus_based = true + +[[parachains.collators]] +name = "trappist-collator01" +image = "docker.io/parity/polkadot-parachain:latest" +command = "trappist-collator" +args = ["--log=xcm=trace,pallet-assets=trace"] + +[[parachains]] +id = 3000 +cumulus_based = true + +[[parachains.collators]] +name = "stout-collator01" +image = "docker.io/parity/polkadot-parachain:latest" +command = "stout-collator" +args = ["--log=xcm=trace,pallet-assets=trace"] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" + +[[hrmp_channels]] +sender = 1000 +recipient = 2000 +max_capacity = 8 +max_message_size = 512 + +[[hrmp_channels]] +sender = 2000 +recipient = 1000 +max_capacity = 8 +max_message_size = 512 + +[[hrmp_channels]] +sender = 1000 +recipient = 3000 +max_capacity = 8 +max_message_size = 512 + +[[hrmp_channels]] +sender = 3000 +recipient = 1000 +max_capacity = 8 +max_message_size = 512 + +[[hrmp_channels]] +sender = 2000 +recipient = 3000 +max_capacity = 8 +max_message_size = 512 + +[[hrmp_channels]] +sender = 3000 +recipient = 2000 +max_capacity = 8 +max_message_size = 512 From 7266c2135ca3c0a9254d18edf910c8f9ef4a3c73 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 21 Mar 2023 10:02:30 -0300 Subject: [PATCH 02/39] feat: add xcm_reserve_transfer example --- examples/xcm_reserve_transfer.ts | 284 ++++++++++++++++++++++++++---- fluent/ExtrinsicRune.ts | 6 + scale_info/overrides/overrides.ts | 8 + words.txt | 5 + 4 files changed, 268 insertions(+), 35 deletions(-) diff --git a/examples/xcm_reserve_transfer.ts b/examples/xcm_reserve_transfer.ts index 0be2d2905..2c4163d25 100644 --- a/examples/xcm_reserve_transfer.ts +++ b/examples/xcm_reserve_transfer.ts @@ -1,51 +1,265 @@ -import { alice } from "capi" import { - ParasSudoWrapper, - Sudo, - System, - types as relayTypes, -} from "zombienet/xcm_playground.toml/alice/@latest/mod.js" -import { - Assets, - // types as statemineTypes, -} from "zombienet/xcm_playground.toml/statemine-collator01/@latest/mod.js" -// import { -// System, -// types, -// XcmPallet, -// } from "zombienet/xcm_playground.toml/trappist-collator01/@latest/mod.js" - -const result = await System.Account.value(alice.publicKey).run() - -console.log(result) - -function setup() { - const forceCreateAsset = Assets.forceCreate({ - id: 1, + alice, + bob, + Chain, + ChainRune, + Era, + hex, + Rune, + RunicArgs, + SignatureData, + ss58, + ValueRune, +} from "capi" +import * as Rococo from "zombienet/xcm_playground.toml/alice/@latest/mod.js" +import * as Statemine from "zombienet/xcm_playground.toml/statemine-collator01/@latest/mod.js" +import * as Trappist from "zombienet/xcm_playground.toml/trappist-collator01/@latest/mod.js" +import { delay } from "../deps/std/async.ts" +import { SignatureProps } from "../patterns/signature/polkadot.ts" + +const ASSET_ID = 1 +const TRAPPIST_ASSET_ID = ASSET_ID +const ASSET_AMOUNT_TO_MINT = 100000000000000n +const ASSET_AMOUNT_TO_SEND = 10000000000000n + +// await sanity() +await setup() +await doReserveTransfer() + +async function setup() { + const forceCreateAssetTransactEncodedCall = await Statemine.Assets.forceCreate({ + id: ASSET_ID, isSufficient: true, minBalance: 1n, owner: alice.address, - }) + }).call + // TODO: .map() to relayTypes.xcm.v2.Instruction.Transact or t.xcm.double_encoded.DoubleEncoded + .run() - const createAsset = Sudo.sudo({ - call: ParasSudoWrapper.sudoQueueDownwardXcm({ + const createReserveAsset = Rococo.Sudo.sudo({ + call: Rococo.ParasSudoWrapper.sudoQueueDownwardXcm({ id: 1000, - xcm: { + xcm: Rune.rec({ type: "V2", - value: [ // convert to Array - relayTypes.xcm.v2.Instruction.Transact({ + value: Rune.array([ + Rococo.types.xcm.v2.Instruction.Transact({ originType: "Superuser", requireWeightAtMost: 1000000000n, - call: forceCreateAsset, // convert to t.xcm.double_encoded.DoubleEncoded + // TODO: convert to t.xcm.double_encoded.DoubleEncoded + call: { + encoded: forceCreateAssetTransactEncodedCall, + }, }), - ], - }, + ]), + }), + }), + }) + + await createReserveAsset + .signed(signature({ sender: alice })) + .sent() + .dbgStatus("ParasSudoWrapper.sudoQueueDownwardXcm") + .finalizedEvents() + .run() + + await waitFor(async () => (await Statemine.Assets.Asset.value(ASSET_ID).run()) !== undefined) // wait for asset id created + console.log("asset created", await Statemine.Assets.Asset.value(ASSET_ID).run()) + + const mintAsset = Statemine.Assets.mint({ + id: ASSET_ID, + amount: ASSET_AMOUNT_TO_MINT, + beneficiary: bob.address, + }) + + await mintAsset + .signed(signature({ sender: alice })) + .sent() + .dbgStatus("mint reserve asset") + .finalizedEvents() + .run() + + console.log("reserve asset minted", await Statemine.Assets.Asset.value(ASSET_ID).run()) + console.log( + "bob reserve asset balance", + await Statemine.Assets.Account.value([ASSET_ID, bob.publicKey]).run(), + ) + + const createDerivedAsset = Trappist.Sudo.sudo({ + call: Trappist.Assets.forceCreate({ + id: TRAPPIST_ASSET_ID, + isSufficient: false, + minBalance: 1n, + owner: alice.address, }), }) - return createAsset + await createDerivedAsset + .signed(signature({ sender: alice })) + .sent() + .dbgStatus("create derived asset") + .finalizedEvents() + .run() + + const { + v1: { + junction: { Junction }, + multilocation: { Junctions }, + }, + } = Trappist.types.xcm + // TODO: batch with createDerivedAsset + const registerReserveAsset = Trappist.Sudo.sudo({ + call: Trappist.AssetRegistry.registerReserveAsset({ + assetId: TRAPPIST_ASSET_ID, + assetMultiLocation: Rune.rec({ + parents: 1, + interior: Junctions.X3( + // TODO: find parachain id + Junction.Parachain(1000), + Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), + Junction.GeneralIndex(BigInt(ASSET_ID)), + ), + }), + }), + }) + + await registerReserveAsset + .signed(signature({ sender: alice })) + .sent() + .dbgStatus("register reserve asset") + .finalizedEvents() + .run() +} + +async function doReserveTransfer() { + const { + VersionedMultiLocation, + VersionedMultiAssets, + v0: { junction: { NetworkId } }, + v1: { + junction: { Junction }, + multilocation: { Junctions }, + multiasset: { AssetId, Fungibility }, + }, + v2: { WeightLimit }, + } = Statemine.types.xcm + const limitedReserveTransferAssets = Statemine.PolkadotXcm.limitedReserveTransferAssets({ + dest: VersionedMultiLocation.V1(Rune.rec({ + parents: 1, + interior: Junctions.X1( + // TODO: find parachain id + Junction.Parachain(2000), + ), + })), + beneficiary: VersionedMultiLocation.V1(Rune.rec({ + parents: 0, + interior: Junctions.X1( + Junction.AccountId32({ + network: NetworkId.Any(), + id: bob.publicKey, + }), + ), + })), + assets: VersionedMultiAssets.V1(Rune.array([Rune.rec({ + id: AssetId.Concrete(Rune.rec({ + parents: 0, + interior: Junctions.X2( + Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), + Junction.GeneralIndex(BigInt(ASSET_ID)), + ), + })), + fun: Fungibility.Fungible(ASSET_AMOUNT_TO_SEND), + })])), + feeAssetItem: 0, + weightLimit: WeightLimit.Unlimited(), + }) + + await limitedReserveTransferAssets + .signed(signature({ sender: bob })) + .sent() + .dbgStatus("limitedReserveTransferAssets") + // TODO: from xcmpQueue.XcmpMessageSent get .messageHash + .finalizedEvents() + .run() + + // TODO: wait for xcmpQueue.(Success|Fail) .messageHash + await waitFor(async () => + await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run() !== undefined + ) // wait for bob balance to update + console.log( + "Trappist Bob asset balance", + await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run(), + ) +} + +async function waitFor( + fn: () => Promise, + delay_ = 1000, +) { + while (true) { + const result = await fn() + if (result) break + await delay(delay_) + } +} + +async function _sanity() { + console.log(await Rococo.System.Account.value(alice.publicKey).run()) + console.log(await Statemine.System.Account.value(alice.publicKey).run()) + console.log(await Trappist.System.Account.value(alice.publicKey).run()) } -if (import.meta.main) { - await setup().run() +function signature(_props: RunicArgs>) { + return (chain: ChainRune) => { + const props = RunicArgs.resolve(_props) + // @ts-ignore FIXME: + const addrPrefix = chain.addressPrefix() + const versions = chain.pallet("System").constant("Version").decoded + const specVersion = versions.access("specVersion") + const transactionVersion = versions.access("transactionVersion") + // TODO: create union rune (with `matchTag` method) and utilize here + // TODO: MultiAddress conversion utils + const senderSs58 = Rune + .tuple([addrPrefix, props.sender]) + .map(([addrPrefix, sender]) => { + switch (sender.address.type) { + case "Id": { + return ss58.encode(addrPrefix, sender.address.value) + } + default: { + throw new Error("unimplemented") + } + } + }) + .throws(ss58.InvalidPayloadLengthError) + const nonce = Rune.resolve(props.nonce) + .unhandle(undefined) + .rehandle(undefined, () => chain.connection.call("system_accountNextIndex", senderSs58)) + const genesisHashHex = chain.connection.call("chain_getBlockHash", 0).unsafeAs() + .into(ValueRune) + const genesisHash = genesisHashHex.map(hex.decode) + const checkpointHash = Rune.tuple([props.checkpoint, genesisHashHex]).map(([a, b]) => a ?? b) + .map(hex.decode) + const mortality = Rune.resolve(props.mortality).map((x) => x ?? Era.Immortal) + const tip = Rune.resolve(props.tip).map((x) => x ?? 0n) + return Rune.rec({ + sender: props.sender, + extra: Rune.rec({ + CheckMortality: mortality, + CheckNonce: nonce, + ChargeTransactionPayment: tip, + ChargeAssetTxPayment: Rune.rec({ + // FIXME: + // assetId: props.assetId, + tip: tip, + }), + }), + additional: Rune.rec({ + CheckSpecVersion: specVersion, + CheckTxVersion: transactionVersion, + CheckGenesis: genesisHash, + CheckMortality: checkpointHash, + }), + // @ts-ignore FIXME: + }) satisfies Rune, unknown> + } } diff --git a/fluent/ExtrinsicRune.ts b/fluent/ExtrinsicRune.ts index 92ceb64c0..8863fd86e 100644 --- a/fluent/ExtrinsicRune.ts +++ b/fluent/ExtrinsicRune.ts @@ -38,6 +38,12 @@ export class ExtrinsicRune extends PatternRune(signatureFactory: SignatureDataFactory) { return Rune .fn($extrinsic) diff --git a/scale_info/overrides/overrides.ts b/scale_info/overrides/overrides.ts index b6a361dad..cdb4e51f7 100644 --- a/scale_info/overrides/overrides.ts +++ b/scale_info/overrides/overrides.ts @@ -46,6 +46,14 @@ export const overrides: Record Codec "frame_support::traits::misc::WrapperKeepOpaque": (ty, visit) => { return $.lenPrefixed(visit(ty.params[0]!.ty!)) }, + "xcm::double_encoded::DoubleEncoded": (ty, visit) => { + console.log("xcm::double_encoded::DoubleEncoded") + return $.lenPrefixed(visit(ty.params[0]!.ty!)) + }, + "xcm::DoubleEncoded": (ty, visit) => { + console.log("xcm::DoubleEncoded") + return $.lenPrefixed(visit(ty.params[0]!.ty!)) + }, "sp_runtime::generic::era::Era": () => { return $era }, diff --git a/words.txt b/words.txt index 37183dc38..96a2c974f 100644 --- a/words.txt +++ b/words.txt @@ -32,10 +32,12 @@ framesystem harrysolovay hasher hashers +hrmp inherents instanceof instantiator kiera +lparachain ltex lxkf matty @@ -53,6 +55,7 @@ offchain parachain parachains paritydb +paritypr paritytech pendings polkadot @@ -86,3 +89,5 @@ unioned vadimcn westend westmint +xcmp +zombienet From dc3405f1a5d908f9b3fcfd25770faccc6004263b Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Wed, 22 Mar 2023 12:50:42 -0300 Subject: [PATCH 03/39] feat: filter XcmpMessageSent.messageHash --- examples/xcm_reserve_transfer.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/xcm_reserve_transfer.ts b/examples/xcm_reserve_transfer.ts index 2c4163d25..8c6b7264e 100644 --- a/examples/xcm_reserve_transfer.ts +++ b/examples/xcm_reserve_transfer.ts @@ -59,7 +59,7 @@ async function setup() { .signed(signature({ sender: alice })) .sent() .dbgStatus("ParasSudoWrapper.sudoQueueDownwardXcm") - .finalizedEvents() + .finalized() .run() await waitFor(async () => (await Statemine.Assets.Asset.value(ASSET_ID).run()) !== undefined) // wait for asset id created @@ -75,7 +75,7 @@ async function setup() { .signed(signature({ sender: alice })) .sent() .dbgStatus("mint reserve asset") - .finalizedEvents() + .finalized() .run() console.log("reserve asset minted", await Statemine.Assets.Asset.value(ASSET_ID).run()) @@ -97,7 +97,7 @@ async function setup() { .signed(signature({ sender: alice })) .sent() .dbgStatus("create derived asset") - .finalizedEvents() + .finalized() .run() const { @@ -126,7 +126,7 @@ async function setup() { .signed(signature({ sender: alice })) .sent() .dbgStatus("register reserve asset") - .finalizedEvents() + .finalized() .run() } @@ -179,6 +179,20 @@ async function doReserveTransfer() { .dbgStatus("limitedReserveTransferAssets") // TODO: from xcmpQueue.XcmpMessageSent get .messageHash .finalizedEvents() + .into(ValueRune) + .map((events) => { + const event = events.find((e) => + Statemine.types.statemine_runtime.RuntimeEvent.isXcmpQueue(e.event) + && Statemine.types.cumulus_pallet_xcmp_queue.pallet.Event.isXcmpMessageSent(e.event.value) + )?.event.value as + | Statemine.types.cumulus_pallet_xcmp_queue.pallet.Event.XcmpMessageSent + | undefined + if (event?.messageHash) { + return hex.encode(event.messageHash) + } + return event + }) + .dbg("XcmpMessageSent.messageHash") .run() // TODO: wait for xcmpQueue.(Success|Fail) .messageHash From eb734590dcc35790011261d8de99a7a6fc0b0309 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Wed, 22 Mar 2023 15:40:26 -0300 Subject: [PATCH 04/39] feat: refactor example --- examples/xcm_reserve_transfer.ts | 252 +++++++++++++++++-------------- words.txt | 1 + zombienets/xcm_playground.toml | 34 ----- 3 files changed, 136 insertions(+), 151 deletions(-) diff --git a/examples/xcm_reserve_transfer.ts b/examples/xcm_reserve_transfer.ts index 8c6b7264e..a13e6ea05 100644 --- a/examples/xcm_reserve_transfer.ts +++ b/examples/xcm_reserve_transfer.ts @@ -4,6 +4,7 @@ import { Chain, ChainRune, Era, + ExtrinsicSender, hex, Rune, RunicArgs, @@ -15,91 +16,92 @@ import * as Rococo from "zombienet/xcm_playground.toml/alice/@latest/mod.js" import * as Statemine from "zombienet/xcm_playground.toml/statemine-collator01/@latest/mod.js" import * as Trappist from "zombienet/xcm_playground.toml/trappist-collator01/@latest/mod.js" import { delay } from "../deps/std/async.ts" -import { SignatureProps } from "../patterns/signature/polkadot.ts" -const ASSET_ID = 1 -const TRAPPIST_ASSET_ID = ASSET_ID -const ASSET_AMOUNT_TO_MINT = 100000000000000n -const ASSET_AMOUNT_TO_SEND = 10000000000000n +/** + * Reserve Transfer Asset example steps + * - Create a sufficient Asset on Reserve Parachain + * - Mint assets on Reserve Parachain + * - Create asset on Trappist Parachain + * - Register Trappist Parachain AssetId to Reserve AssetId + * - Reserve transfer AssetId on Reserve Parachain to Trappist Parachain + */ -// await sanity() -await setup() -await doReserveTransfer() +const RESERVE_ASSET_ID = 1 +const RESERVE_CHAIN_ID = 1000 // Statemine +const TRAPPIST_ASSET_ID = RESERVE_ASSET_ID +const TRAPPIST_CHAIN_ID = 2000 +const RESERVE_ASSET_AMOUNT_TO_MINT = 100000000000000n +const RESERVE_ASSET_AMOUNT_TO_SEND = 10000000000000n -async function setup() { - const forceCreateAssetTransactEncodedCall = await Statemine.Assets.forceCreate({ - id: ASSET_ID, - isSufficient: true, - minBalance: 1n, - owner: alice.address, - }).call - // TODO: .map() to relayTypes.xcm.v2.Instruction.Transact or t.xcm.double_encoded.DoubleEncoded - .run() - - const createReserveAsset = Rococo.Sudo.sudo({ - call: Rococo.ParasSudoWrapper.sudoQueueDownwardXcm({ - id: 1000, - xcm: Rune.rec({ - type: "V2", - value: Rune.array([ - Rococo.types.xcm.v2.Instruction.Transact({ - originType: "Superuser", - requireWeightAtMost: 1000000000n, - // TODO: convert to t.xcm.double_encoded.DoubleEncoded - call: { - encoded: forceCreateAssetTransactEncodedCall, - }, +// In Statemine, root is needed to create a sufficient asset +// In a common good parachain, root is accessed through the relay chain +await Rococo.Sudo.sudo({ + call: Rococo.ParasSudoWrapper.sudoQueueDownwardXcm({ + id: RESERVE_CHAIN_ID, + xcm: Rune.rec({ + type: "V2", + value: Rune.array([ + Rococo.types.xcm.v2.Instruction.Transact({ + originType: "Superuser", + requireWeightAtMost: 1000000000n, + call: Rune.rec({ + encoded: Statemine.Assets.forceCreate({ + id: RESERVE_ASSET_ID, + isSufficient: true, + minBalance: 1n, + owner: alice.address, + }).call, }), - ]), - }), + }), + ]), }), - }) + }), +}) + .signed(signature({ sender: alice })) + .sent() + .dbgStatus("Rococo(root) > Statemine(root): Create asset") + .finalized() + .run() - await createReserveAsset - .signed(signature({ sender: alice })) - .sent() - .dbgStatus("ParasSudoWrapper.sudoQueueDownwardXcm") - .finalized() - .run() - - await waitFor(async () => (await Statemine.Assets.Asset.value(ASSET_ID).run()) !== undefined) // wait for asset id created - console.log("asset created", await Statemine.Assets.Asset.value(ASSET_ID).run()) - - const mintAsset = Statemine.Assets.mint({ - id: ASSET_ID, - amount: ASSET_AMOUNT_TO_MINT, - beneficiary: bob.address, - }) - - await mintAsset - .signed(signature({ sender: alice })) - .sent() - .dbgStatus("mint reserve asset") - .finalized() - .run() +await waitFor(async () => + (await Statemine.Assets.Asset.value(RESERVE_ASSET_ID).run()) !== undefined +) +console.log( + "Statemine: Asset created", + await Statemine.Assets.Asset.value(RESERVE_ASSET_ID).run(), +) - console.log("reserve asset minted", await Statemine.Assets.Asset.value(ASSET_ID).run()) - console.log( - "bob reserve asset balance", - await Statemine.Assets.Account.value([ASSET_ID, bob.publicKey]).run(), - ) +await Statemine.Assets.mint({ + id: RESERVE_ASSET_ID, + amount: RESERVE_ASSET_AMOUNT_TO_MINT, + beneficiary: bob.address, +}) + .signed(signature({ sender: alice })) + .sent() + .dbgStatus("Statemine(Alice): Mint reserve asset to Bob") + .finalized() + .run() - const createDerivedAsset = Trappist.Sudo.sudo({ - call: Trappist.Assets.forceCreate({ - id: TRAPPIST_ASSET_ID, - isSufficient: false, - minBalance: 1n, - owner: alice.address, - }), - }) +console.log( + "Statemine(Bob): asset balance", + await Statemine.Assets.Account.value([RESERVE_ASSET_ID, bob.publicKey]).run(), +) - await createDerivedAsset - .signed(signature({ sender: alice })) - .sent() - .dbgStatus("create derived asset") - .finalized() - .run() +await Trappist.Sudo.sudo({ + call: Trappist.Assets.forceCreate({ + id: TRAPPIST_ASSET_ID, + isSufficient: false, + minBalance: 1n, + owner: alice.address, + }), +}) + .signed(signature({ sender: alice })) + .sent() + .dbgStatus("Trappist(root): Create derived asset") + .finalized() + .run() +{ const { v1: { junction: { Junction }, @@ -107,47 +109,49 @@ async function setup() { }, } = Trappist.types.xcm // TODO: batch with createDerivedAsset - const registerReserveAsset = Trappist.Sudo.sudo({ + await Trappist.Sudo.sudo({ call: Trappist.AssetRegistry.registerReserveAsset({ assetId: TRAPPIST_ASSET_ID, assetMultiLocation: Rune.rec({ parents: 1, interior: Junctions.X3( // TODO: find parachain id - Junction.Parachain(1000), + Junction.Parachain(RESERVE_CHAIN_ID), Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), - Junction.GeneralIndex(BigInt(ASSET_ID)), + Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), ), }), }), }) - - await registerReserveAsset .signed(signature({ sender: alice })) .sent() - .dbgStatus("register reserve asset") + .dbgStatus("Trappist(root): Register AssetId to Reserve AssetId") .finalized() .run() } -async function doReserveTransfer() { +{ const { - VersionedMultiLocation, - VersionedMultiAssets, - v0: { junction: { NetworkId } }, - v1: { - junction: { Junction }, - multilocation: { Junctions }, - multiasset: { AssetId, Fungibility }, + xcm: { + VersionedMultiLocation, + VersionedMultiAssets, + v0: { junction: { NetworkId } }, + v1: { + junction: { Junction }, + multilocation: { Junctions }, + multiasset: { AssetId, Fungibility }, + }, + v2: { WeightLimit }, }, - v2: { WeightLimit }, - } = Statemine.types.xcm - const limitedReserveTransferAssets = Statemine.PolkadotXcm.limitedReserveTransferAssets({ + statemine_runtime: { RuntimeEvent }, + cumulus_pallet_xcmp_queue: { pallet: { Event } }, + } = Statemine.types + await Statemine.PolkadotXcm.limitedReserveTransferAssets({ dest: VersionedMultiLocation.V1(Rune.rec({ parents: 1, interior: Junctions.X1( // TODO: find parachain id - Junction.Parachain(2000), + Junction.Parachain(TRAPPIST_CHAIN_ID), ), })), beneficiary: VersionedMultiLocation.V1(Rune.rec({ @@ -164,26 +168,23 @@ async function doReserveTransfer() { parents: 0, interior: Junctions.X2( Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), - Junction.GeneralIndex(BigInt(ASSET_ID)), + Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), ), })), - fun: Fungibility.Fungible(ASSET_AMOUNT_TO_SEND), + fun: Fungibility.Fungible(RESERVE_ASSET_AMOUNT_TO_SEND), })])), feeAssetItem: 0, weightLimit: WeightLimit.Unlimited(), }) - - await limitedReserveTransferAssets .signed(signature({ sender: bob })) .sent() - .dbgStatus("limitedReserveTransferAssets") - // TODO: from xcmpQueue.XcmpMessageSent get .messageHash + .dbgStatus("Statemine(Bob): Reserve transfer to Trappist") .finalizedEvents() .into(ValueRune) .map((events) => { const event = events.find((e) => - Statemine.types.statemine_runtime.RuntimeEvent.isXcmpQueue(e.event) - && Statemine.types.cumulus_pallet_xcmp_queue.pallet.Event.isXcmpMessageSent(e.event.value) + RuntimeEvent.isXcmpQueue(e.event) + && Event.isXcmpMessageSent(e.event.value) )?.event.value as | Statemine.types.cumulus_pallet_xcmp_queue.pallet.Event.XcmpMessageSent | undefined @@ -194,32 +195,49 @@ async function doReserveTransfer() { }) .dbg("XcmpMessageSent.messageHash") .run() - - // TODO: wait for xcmpQueue.(Success|Fail) .messageHash - await waitFor(async () => - await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run() !== undefined - ) // wait for bob balance to update - console.log( - "Trappist Bob asset balance", - await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run(), - ) } +// TODO: wait for xcmpQueue.(Success|Fail) .messageHash +await waitFor(async () => + await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run() !== undefined +) +console.log( + "Trappist(Bob): asset balance", + await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run(), +) +console.log( + "Statemine(Bob): asset balance", + await Statemine.Assets.Account.value([RESERVE_ASSET_ID, bob.publicKey]).run(), +) +console.log( + "Statemine(TrappistSovereignAccount): asset balance", + await Statemine.Assets.Account.value([ + RESERVE_ASSET_ID, + // Sovereign address on sibling chain + // b"sibl" + $.u32.encode(2000) + 0...0 + hex.decode("0x7369626cd0070000000000000000000000000000000000000000000000000000"), + ]).run(), +) + async function waitFor( fn: () => Promise, delay_ = 1000, + maxAttempts = 60, ) { - while (true) { - const result = await fn() - if (result) break + let attempts = 0 + while (attempts++ < maxAttempts) { + if (await fn()) return await delay(delay_) } + throw new Error("waitFor maxAttempts reached") } -async function _sanity() { - console.log(await Rococo.System.Account.value(alice.publicKey).run()) - console.log(await Statemine.System.Account.value(alice.publicKey).run()) - console.log(await Trappist.System.Account.value(alice.publicKey).run()) +interface SignatureProps { + sender: ExtrinsicSender + checkpoint?: string + mortality?: Era + nonce?: number + tip?: bigint } function signature(_props: RunicArgs>) { diff --git a/words.txt b/words.txt index 96a2c974f..5b0decbbc 100644 --- a/words.txt +++ b/words.txt @@ -67,6 +67,7 @@ runtimes ryann serde shiki +sibl smoldot statemigration statemine diff --git a/zombienets/xcm_playground.toml b/zombienets/xcm_playground.toml index 88e0c2c4b..01e23917f 100644 --- a/zombienets/xcm_playground.toml +++ b/zombienets/xcm_playground.toml @@ -42,16 +42,6 @@ image = "docker.io/parity/polkadot-parachain:latest" command = "trappist-collator" args = ["--log=xcm=trace,pallet-assets=trace"] -[[parachains]] -id = 3000 -cumulus_based = true - -[[parachains.collators]] -name = "stout-collator01" -image = "docker.io/parity/polkadot-parachain:latest" -command = "stout-collator" -args = ["--log=xcm=trace,pallet-assets=trace"] - [types.Header] number = "u64" parent_hash = "Hash" @@ -68,27 +58,3 @@ sender = 2000 recipient = 1000 max_capacity = 8 max_message_size = 512 - -[[hrmp_channels]] -sender = 1000 -recipient = 3000 -max_capacity = 8 -max_message_size = 512 - -[[hrmp_channels]] -sender = 3000 -recipient = 1000 -max_capacity = 8 -max_message_size = 512 - -[[hrmp_channels]] -sender = 2000 -recipient = 3000 -max_capacity = 8 -max_message_size = 512 - -[[hrmp_channels]] -sender = 3000 -recipient = 2000 -max_capacity = 8 -max_message_size = 512 From 191b376124a95ef7f59a8de2bbdb59960731f975 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Thu, 23 Mar 2023 10:28:26 -0300 Subject: [PATCH 05/39] feat: remove xcm::double_encoded::DoubleEncoded override --- scale_info/overrides/overrides.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/scale_info/overrides/overrides.ts b/scale_info/overrides/overrides.ts index cdb4e51f7..b6a361dad 100644 --- a/scale_info/overrides/overrides.ts +++ b/scale_info/overrides/overrides.ts @@ -46,14 +46,6 @@ export const overrides: Record Codec "frame_support::traits::misc::WrapperKeepOpaque": (ty, visit) => { return $.lenPrefixed(visit(ty.params[0]!.ty!)) }, - "xcm::double_encoded::DoubleEncoded": (ty, visit) => { - console.log("xcm::double_encoded::DoubleEncoded") - return $.lenPrefixed(visit(ty.params[0]!.ty!)) - }, - "xcm::DoubleEncoded": (ty, visit) => { - console.log("xcm::DoubleEncoded") - return $.lenPrefixed(visit(ty.params[0]!.ty!)) - }, "sp_runtime::generic::era::Era": () => { return $era }, From 21a269a73f89eb7c4cf96b29a420e46d5ad7d816 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 28 Mar 2023 13:34:58 -0300 Subject: [PATCH 06/39] feat: use capi-binary-builds --- examples/xcm_reserve_transfer.ts | 6 +++--- zombienets/{xcm_playground.toml => trappist.toml} | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename zombienets/{xcm_playground.toml => trappist.toml} (85%) diff --git a/examples/xcm_reserve_transfer.ts b/examples/xcm_reserve_transfer.ts index a13e6ea05..4c786b5d7 100644 --- a/examples/xcm_reserve_transfer.ts +++ b/examples/xcm_reserve_transfer.ts @@ -12,9 +12,9 @@ import { ss58, ValueRune, } from "capi" -import * as Rococo from "zombienet/xcm_playground.toml/alice/@latest/mod.js" -import * as Statemine from "zombienet/xcm_playground.toml/statemine-collator01/@latest/mod.js" -import * as Trappist from "zombienet/xcm_playground.toml/trappist-collator01/@latest/mod.js" +import * as Rococo from "zombienet/trappist.toml/alice/@latest/mod.js" +import * as Statemine from "zombienet/trappist.toml/statemine-collator01/@latest/mod.js" +import * as Trappist from "zombienet/trappist.toml/trappist-collator01/@latest/mod.js" import { delay } from "../deps/std/async.ts" /** diff --git a/zombienets/xcm_playground.toml b/zombienets/trappist.toml similarity index 85% rename from zombienets/xcm_playground.toml rename to zombienets/trappist.toml index 01e23917f..2079462ed 100644 --- a/zombienets/xcm_playground.toml +++ b/zombienets/trappist.toml @@ -3,7 +3,7 @@ timeout = 1000 [relaychain] default_image = "docker.io/paritypr/polkadot-debug:master" -default_command = "polkadot" +default_command = "`deno task capi bin polkadot v0.9.37`" chain = "rococo-local" [[relaychain.nodes]] @@ -29,7 +29,7 @@ chain = "statemine-local" [[parachains.collators]] name = "statemine-collator01" image = "docker.io/parity/polkadot-parachain:latest" -command = "polkadot-parachain" +command = "`deno task capi bin polkadot-parachain v0.9.370`" args = ["--log=xcm=trace,pallet-assets=trace"] [[parachains]] @@ -39,7 +39,7 @@ cumulus_based = true [[parachains.collators]] name = "trappist-collator01" image = "docker.io/parity/polkadot-parachain:latest" -command = "trappist-collator" +command = "`deno task capi bin trappist-collator 22755b2`" args = ["--log=xcm=trace,pallet-assets=trace"] [types.Header] From 0929037cace20c219564e346fbb2084464df17bf Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 28 Mar 2023 13:46:15 -0300 Subject: [PATCH 07/39] rever: patterns/signature/polkadot.ts --- patterns/signature/polkadot.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/patterns/signature/polkadot.ts b/patterns/signature/polkadot.ts index 25c4fac79..ece29ac5d 100644 --- a/patterns/signature/polkadot.ts +++ b/patterns/signature/polkadot.ts @@ -5,8 +5,8 @@ import { $, hex, ss58, ValueRune } from "../../mod.ts" import { Rune, RunicArgs } from "../../rune/Rune.ts" import { Era } from "../../scale_info/overrides/Era.ts" -export interface SignatureProps { - sender: ExtrinsicSender +export interface SignatureProps { + sender: ExtrinsicSender checkpoint?: string mortality?: Era nonce?: number @@ -28,7 +28,7 @@ export interface PolkadotSignatureChain extends AddressPrefixChain { } } -export function signature(_props: RunicArgs>) { +export function signature(_props: RunicArgs) { return (chain: ChainRune) => { const props = RunicArgs.resolve(_props) const addrPrefix = chain.addressPrefix() From f270368ea4f7f34ad9c6a4f291e837495eea1d44 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 28 Mar 2023 13:48:25 -0300 Subject: [PATCH 08/39] chore: remove todo comments --- examples/xcm_reserve_transfer.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/xcm_reserve_transfer.ts b/examples/xcm_reserve_transfer.ts index 4c786b5d7..36176408e 100644 --- a/examples/xcm_reserve_transfer.ts +++ b/examples/xcm_reserve_transfer.ts @@ -108,14 +108,12 @@ await Trappist.Sudo.sudo({ multilocation: { Junctions }, }, } = Trappist.types.xcm - // TODO: batch with createDerivedAsset await Trappist.Sudo.sudo({ call: Trappist.AssetRegistry.registerReserveAsset({ assetId: TRAPPIST_ASSET_ID, assetMultiLocation: Rune.rec({ parents: 1, interior: Junctions.X3( - // TODO: find parachain id Junction.Parachain(RESERVE_CHAIN_ID), Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), @@ -150,7 +148,6 @@ await Trappist.Sudo.sudo({ dest: VersionedMultiLocation.V1(Rune.rec({ parents: 1, interior: Junctions.X1( - // TODO: find parachain id Junction.Parachain(TRAPPIST_CHAIN_ID), ), })), @@ -197,7 +194,6 @@ await Trappist.Sudo.sudo({ .run() } -// TODO: wait for xcmpQueue.(Success|Fail) .messageHash await waitFor(async () => await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run() !== undefined ) From 7e0bebc69e6e5755b62ec045ddf5b19c60680764 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Fri, 31 Mar 2023 12:14:24 -0300 Subject: [PATCH 09/39] chore: move xcm/reserve_transfer.ts example --- examples/{xcm_reserve_transfer.ts => xcm/reserve_transfer.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename examples/{xcm_reserve_transfer.ts => xcm/reserve_transfer.ts} (99%) diff --git a/examples/xcm_reserve_transfer.ts b/examples/xcm/reserve_transfer.ts similarity index 99% rename from examples/xcm_reserve_transfer.ts rename to examples/xcm/reserve_transfer.ts index 36176408e..b642daec3 100644 --- a/examples/xcm_reserve_transfer.ts +++ b/examples/xcm/reserve_transfer.ts @@ -15,7 +15,7 @@ import { import * as Rococo from "zombienet/trappist.toml/alice/@latest/mod.js" import * as Statemine from "zombienet/trappist.toml/statemine-collator01/@latest/mod.js" import * as Trappist from "zombienet/trappist.toml/trappist-collator01/@latest/mod.js" -import { delay } from "../deps/std/async.ts" +import { delay } from "../../deps/std/async.ts" /** * Reserve Transfer Asset example steps From fe2e96cabf4be51184edfcd78353d9407b341966 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Mon, 3 Apr 2023 17:55:32 -0300 Subject: [PATCH 10/39] chore: rename reserve_transfer.eg.ts --- examples/xcm/{reserve_transfer.ts => reserve_transfer.eg.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/xcm/{reserve_transfer.ts => reserve_transfer.eg.ts} (100%) diff --git a/examples/xcm/reserve_transfer.ts b/examples/xcm/reserve_transfer.eg.ts similarity index 100% rename from examples/xcm/reserve_transfer.ts rename to examples/xcm/reserve_transfer.eg.ts From dc76792bed26e3c5dffac813533f2e878cc7e76a Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Wed, 5 Apr 2023 17:45:06 -0400 Subject: [PATCH 11/39] some tweaks (#851) --- examples/xcm/reserve_transfer.eg.ts | 338 ++++++++++------------------ fluent/ChainRune.ts | 2 +- patterns/signature/statemint.ts | 73 ++++++ util/mod.ts | 1 + util/waitFor.ts | 10 + 5 files changed, 210 insertions(+), 214 deletions(-) create mode 100644 patterns/signature/statemint.ts create mode 100644 util/waitFor.ts diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index b642daec3..632a952ac 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -1,62 +1,49 @@ -import { - alice, - bob, - Chain, - ChainRune, - Era, - ExtrinsicSender, - hex, - Rune, - RunicArgs, - SignatureData, - ss58, - ValueRune, -} from "capi" +/** + * @title XCM Reserve Asset Transfer + * @stability nearing + * @description Perform an XCM reserve asset transfer, in which two chains, which + * do not trust one another, rely on a third chain to store assets and facilitate + * the exchange. + */ + +import { alice, bob, hex, Rune, ValueRune } from "capi" +import { signature } from "capi/patterns/signature/statemint.ts" import * as Rococo from "zombienet/trappist.toml/alice/@latest/mod.js" import * as Statemine from "zombienet/trappist.toml/statemine-collator01/@latest/mod.js" import * as Trappist from "zombienet/trappist.toml/trappist-collator01/@latest/mod.js" -import { delay } from "../../deps/std/async.ts" - -/** - * Reserve Transfer Asset example steps - * - Create a sufficient Asset on Reserve Parachain - * - Mint assets on Reserve Parachain - * - Create asset on Trappist Parachain - * - Register Trappist Parachain AssetId to Reserve AssetId - * - Reserve transfer AssetId on Reserve Parachain to Trappist Parachain - */ +import { waitFor } from "../../util/mod.ts" +// Define some constants for later use. const RESERVE_ASSET_ID = 1 const RESERVE_CHAIN_ID = 1000 // Statemine const TRAPPIST_ASSET_ID = RESERVE_ASSET_ID const TRAPPIST_CHAIN_ID = 2000 -const RESERVE_ASSET_AMOUNT_TO_MINT = 100000000000000n -const RESERVE_ASSET_AMOUNT_TO_SEND = 10000000000000n -// In Statemine, root is needed to create a sufficient asset -// In a common good parachain, root is accessed through the relay chain -await Rococo.Sudo.sudo({ - call: Rococo.ParasSudoWrapper.sudoQueueDownwardXcm({ - id: RESERVE_CHAIN_ID, - xcm: Rune.rec({ - type: "V2", - value: Rune.array([ - Rococo.types.xcm.v2.Instruction.Transact({ - originType: "Superuser", - requireWeightAtMost: 1000000000n, - call: Rune.rec({ - encoded: Statemine.Assets.forceCreate({ - id: RESERVE_ASSET_ID, - isSufficient: true, - minBalance: 1n, - owner: alice.address, - }).call, +// Create a sufficient asset with Sudo. When targeting a common good +// parachain, access root instead through the relay chain. +await Rococo.Sudo + .sudo({ + call: Rococo.ParasSudoWrapper.sudoQueueDownwardXcm({ + id: RESERVE_CHAIN_ID, + xcm: Rune.rec({ + type: "V2", + value: Rune.tuple([ + Rococo.types.xcm.v2.Instruction.Transact({ + originType: "Superuser", + requireWeightAtMost: 1000000000n, + call: Rune.rec({ + encoded: Statemine.Assets.forceCreate({ + id: RESERVE_ASSET_ID, + isSufficient: true, + minBalance: 1n, + owner: alice.address, + }).call, + }), }), - }), - ]), + ]), + }), }), - }), -}) + }) .signed(signature({ sender: alice })) .sent() .dbgStatus("Rococo(root) > Statemine(root): Create asset") @@ -71,56 +58,60 @@ console.log( await Statemine.Assets.Asset.value(RESERVE_ASSET_ID).run(), ) -await Statemine.Assets.mint({ - id: RESERVE_ASSET_ID, - amount: RESERVE_ASSET_AMOUNT_TO_MINT, - beneficiary: bob.address, -}) +// Mint assets on reserve parachain. +await Statemine.Assets + .mint({ + id: RESERVE_ASSET_ID, + amount: 100000000000000n, + beneficiary: bob.address, + }) .signed(signature({ sender: alice })) .sent() .dbgStatus("Statemine(Alice): Mint reserve asset to Bob") .finalized() .run() -console.log( - "Statemine(Bob): asset balance", - await Statemine.Assets.Account.value([RESERVE_ASSET_ID, bob.publicKey]).run(), -) +const bobStatemintBalance = Statemine.Assets.Account + .value([RESERVE_ASSET_ID, bob.publicKey]) + .unhandle(undefined) + .access("balance") + +const bobStatemintBalanceInitial = await bobStatemintBalance.run() +console.log("Statemine(Bob): asset balance", bobStatemintBalanceInitial) -await Trappist.Sudo.sudo({ - call: Trappist.Assets.forceCreate({ - id: TRAPPIST_ASSET_ID, - isSufficient: false, - minBalance: 1n, - owner: alice.address, - }), -}) +// Create the asset on the Trappist parachain. +await Trappist.Sudo + .sudo({ + call: Trappist.Assets.forceCreate({ + id: TRAPPIST_ASSET_ID, + isSufficient: false, + minBalance: 1n, + owner: alice.address, + }), + }) .signed(signature({ sender: alice })) .sent() .dbgStatus("Trappist(root): Create derived asset") .finalized() .run() +// Register Trappist parachain asset id to reserve asset id. { - const { - v1: { - junction: { Junction }, - multilocation: { Junctions }, - }, - } = Trappist.types.xcm - await Trappist.Sudo.sudo({ - call: Trappist.AssetRegistry.registerReserveAsset({ - assetId: TRAPPIST_ASSET_ID, - assetMultiLocation: Rune.rec({ - parents: 1, - interior: Junctions.X3( - Junction.Parachain(RESERVE_CHAIN_ID), - Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), - Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), - ), + const { v1: { junction: { Junction }, multilocation } } = Trappist.types.xcm + await Trappist.Sudo + .sudo({ + call: Trappist.AssetRegistry.registerReserveAsset({ + assetId: TRAPPIST_ASSET_ID, + assetMultiLocation: Rune.rec({ + parents: 1, + interior: multilocation.Junctions.X3( + Junction.Parachain(RESERVE_CHAIN_ID), + Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), + Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), + ), + }), }), - }), - }) + }) .signed(signature({ sender: alice })) .sent() .dbgStatus("Trappist(root): Register AssetId to Reserve AssetId") @@ -128,6 +119,7 @@ await Trappist.Sudo.sudo({ .run() } +// Reserve transfer asset id on reserve parachain to Trappist parachain. { const { xcm: { @@ -144,150 +136,70 @@ await Trappist.Sudo.sudo({ statemine_runtime: { RuntimeEvent }, cumulus_pallet_xcmp_queue: { pallet: { Event } }, } = Statemine.types - await Statemine.PolkadotXcm.limitedReserveTransferAssets({ - dest: VersionedMultiLocation.V1(Rune.rec({ - parents: 1, - interior: Junctions.X1( - Junction.Parachain(TRAPPIST_CHAIN_ID), - ), - })), - beneficiary: VersionedMultiLocation.V1(Rune.rec({ - parents: 0, - interior: Junctions.X1( - Junction.AccountId32({ - network: NetworkId.Any(), - id: bob.publicKey, - }), - ), - })), - assets: VersionedMultiAssets.V1(Rune.array([Rune.rec({ - id: AssetId.Concrete(Rune.rec({ + await Statemine.PolkadotXcm + .limitedReserveTransferAssets({ + dest: VersionedMultiLocation.V1(Rune.rec({ + parents: 1, + interior: Junctions.X1( + Junction.Parachain(TRAPPIST_CHAIN_ID), + ), + })), + beneficiary: VersionedMultiLocation.V1(Rune.rec({ parents: 0, - interior: Junctions.X2( - Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), - Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), + interior: Junctions.X1( + Junction.AccountId32({ + network: NetworkId.Any(), + id: bob.publicKey, + }), ), })), - fun: Fungibility.Fungible(RESERVE_ASSET_AMOUNT_TO_SEND), - })])), - feeAssetItem: 0, - weightLimit: WeightLimit.Unlimited(), - }) + assets: VersionedMultiAssets.V1(Rune.array([Rune.rec({ + id: AssetId.Concrete(Rune.rec({ + parents: 0, + interior: Junctions.X2( + Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), + Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), + ), + })), + fun: Fungibility.Fungible(10000000000000n), + })])), + feeAssetItem: 0, + weightLimit: WeightLimit.Unlimited(), + }) .signed(signature({ sender: bob })) .sent() .dbgStatus("Statemine(Bob): Reserve transfer to Trappist") .finalizedEvents() .into(ValueRune) .map((events) => { - const event = events.find((e) => - RuntimeEvent.isXcmpQueue(e.event) - && Event.isXcmpMessageSent(e.event.value) - )?.event.value as - | Statemine.types.cumulus_pallet_xcmp_queue.pallet.Event.XcmpMessageSent - | undefined - if (event?.messageHash) { - return hex.encode(event.messageHash) - } - return event + const event = events + .find((e) => RuntimeEvent.isXcmpQueue(e.event) && Event.isXcmpMessageSent(e.event.value)) + ?.event.value as + | Statemine.types.cumulus_pallet_xcmp_queue.pallet.Event.XcmpMessageSent + | undefined + return event?.messageHash ? hex.encode(event.messageHash) : event }) .dbg("XcmpMessageSent.messageHash") .run() } -await waitFor(async () => - await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run() !== undefined -) -console.log( - "Trappist(Bob): asset balance", - await Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]).run(), -) -console.log( - "Statemine(Bob): asset balance", - await Statemine.Assets.Account.value([RESERVE_ASSET_ID, bob.publicKey]).run(), -) -console.log( - "Statemine(TrappistSovereignAccount): asset balance", - await Statemine.Assets.Account.value([ - RESERVE_ASSET_ID, - // Sovereign address on sibling chain - // b"sibl" + $.u32.encode(2000) + 0...0 - hex.decode("0x7369626cd0070000000000000000000000000000000000000000000000000000"), - ]).run(), -) +const bobTrappistAssetAccount = Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]) -async function waitFor( - fn: () => Promise, - delay_ = 1000, - maxAttempts = 60, -) { - let attempts = 0 - while (attempts++ < maxAttempts) { - if (await fn()) return - await delay(delay_) - } - throw new Error("waitFor maxAttempts reached") -} +await waitFor(async () => !!await bobTrappistAssetAccount.run()) -interface SignatureProps { - sender: ExtrinsicSender - checkpoint?: string - mortality?: Era - nonce?: number - tip?: bigint -} +const bobTrappistBalance = await bobTrappistAssetAccount + .unhandle(undefined).access("balance").run() +console.log("Trappist(Bob): asset balance:", bobTrappistBalance) -function signature(_props: RunicArgs>) { - return (chain: ChainRune) => { - const props = RunicArgs.resolve(_props) - // @ts-ignore FIXME: - const addrPrefix = chain.addressPrefix() - const versions = chain.pallet("System").constant("Version").decoded - const specVersion = versions.access("specVersion") - const transactionVersion = versions.access("transactionVersion") - // TODO: create union rune (with `matchTag` method) and utilize here - // TODO: MultiAddress conversion utils - const senderSs58 = Rune - .tuple([addrPrefix, props.sender]) - .map(([addrPrefix, sender]) => { - switch (sender.address.type) { - case "Id": { - return ss58.encode(addrPrefix, sender.address.value) - } - default: { - throw new Error("unimplemented") - } - } - }) - .throws(ss58.InvalidPayloadLengthError) - const nonce = Rune.resolve(props.nonce) - .unhandle(undefined) - .rehandle(undefined, () => chain.connection.call("system_accountNextIndex", senderSs58)) - const genesisHashHex = chain.connection.call("chain_getBlockHash", 0).unsafeAs() - .into(ValueRune) - const genesisHash = genesisHashHex.map(hex.decode) - const checkpointHash = Rune.tuple([props.checkpoint, genesisHashHex]).map(([a, b]) => a ?? b) - .map(hex.decode) - const mortality = Rune.resolve(props.mortality).map((x) => x ?? Era.Immortal) - const tip = Rune.resolve(props.tip).map((x) => x ?? 0n) - return Rune.rec({ - sender: props.sender, - extra: Rune.rec({ - CheckMortality: mortality, - CheckNonce: nonce, - ChargeTransactionPayment: tip, - ChargeAssetTxPayment: Rune.rec({ - // FIXME: - // assetId: props.assetId, - tip: tip, - }), - }), - additional: Rune.rec({ - CheckSpecVersion: specVersion, - CheckTxVersion: transactionVersion, - CheckGenesis: genesisHash, - CheckMortality: checkpointHash, - }), - // @ts-ignore FIXME: - }) satisfies Rune, unknown> - } -} +const bobStatemintBalanceFinal = await bobStatemintBalance.run() +console.log("Statemine(Bob): asset balance:", bobStatemintBalanceFinal) + +const statemintSovereignAccountBalance = await Statemine.Assets.Account + // Sovereign address on sibling chain + // b"sibl" + $.u32.encode(2000) + 0...0 + .value([ + RESERVE_ASSET_ID, + hex.decode("0x7369626cd0070000000000000000000000000000000000000000000000000000"), + ]) + .run() +console.log("Statemine(TrappistSovereignAccount): asset balance", statemintSovereignAccountBalance) diff --git a/fluent/ChainRune.ts b/fluent/ChainRune.ts index 0e686e0f6..1cec128c4 100644 --- a/fluent/ChainRune.ts +++ b/fluent/ChainRune.ts @@ -101,7 +101,7 @@ export class ChainRune extends Rune { .into(PalletRune, this.as(ChainRune)) } - addressPrefix(this: ChainRune) { + addressPrefix() { return this .pallet("System") .constant("SS58Prefix") diff --git a/patterns/signature/statemint.ts b/patterns/signature/statemint.ts new file mode 100644 index 000000000..f221fd3aa --- /dev/null +++ b/patterns/signature/statemint.ts @@ -0,0 +1,73 @@ +import { + Chain, + ChainRune, + Era, + ExtrinsicSender, + hex, + Rune, + RunicArgs, + SignatureData, + ss58, + ValueRune, +} from "../../mod.ts" + +export interface SignatureProps { + sender: ExtrinsicSender + checkpoint?: string + mortality?: Era + nonce?: number + tip?: bigint +} + +export function signature(_props: RunicArgs>) { + return (chain: ChainRune) => { + const props = RunicArgs.resolve(_props) + const addrPrefix = chain.addressPrefix() + const versions = chain.pallet("System").constant("Version").decoded + const specVersion = versions.access("specVersion") + const transactionVersion = versions.access("transactionVersion") + // TODO: create union rune (with `matchTag` method) and utilize here + // TODO: MultiAddress conversion utils + const senderSs58 = Rune + .tuple([addrPrefix, props.sender]) + .map(([addrPrefix, sender]) => { + switch (sender.address.type) { + case "Id": + return ss58.encode(addrPrefix, sender.address.value) + default: + throw new Error("unimplemented") + } + }) + .throws(ss58.InvalidPayloadLengthError) + const nonce = Rune.resolve(props.nonce) + .unhandle(undefined) + .rehandle(undefined, () => chain.connection.call("system_accountNextIndex", senderSs58)) + const genesisHashHex = chain.connection.call("chain_getBlockHash", 0).unsafeAs() + .into(ValueRune) + const genesisHash = genesisHashHex.map(hex.decode) + const checkpointHash = Rune.tuple([props.checkpoint, genesisHashHex]).map(([a, b]) => a ?? b) + .map(hex.decode) + const mortality = Rune.resolve(props.mortality).map((x) => x ?? Era.Immortal) + const tip = Rune.resolve(props.tip).map((x) => x ?? 0n) + return Rune.rec({ + sender: props.sender, + extra: Rune.rec({ + CheckMortality: mortality, + CheckNonce: nonce, + ChargeTransactionPayment: tip, + ChargeAssetTxPayment: Rune.rec({ + // TODO: + // assetId: props.assetId, + tip: tip, + }), + }), + additional: Rune.rec({ + CheckSpecVersion: specVersion, + CheckTxVersion: transactionVersion, + CheckGenesis: genesisHash, + CheckMortality: checkpointHash, + }), + // @ts-ignore: . + }) satisfies Rune, unknown> + } +} diff --git a/util/mod.ts b/util/mod.ts index e378e49c6..eaeb77794 100644 --- a/util/mod.ts +++ b/util/mod.ts @@ -10,4 +10,5 @@ export * from "./memo.ts" export * from "./normalize.ts" export * from "./notifier.ts" export * from "./state.ts" +export * from "./waitFor.ts" export * from "./withSignal.ts" diff --git a/util/waitFor.ts b/util/waitFor.ts new file mode 100644 index 000000000..620f59c06 --- /dev/null +++ b/util/waitFor.ts @@ -0,0 +1,10 @@ +import { delay } from "../deps/std/async.ts" + +export async function waitFor(fn: () => Promise, interval = 1000, maxAttempts = 60) { + let attempts = 0 + while (attempts++ < maxAttempts) { + if (await fn()) return + await delay(interval) + } + throw new Error("waitFor maxAttempts reached") +} From 0771a5f623fa6e944885cd3d79e849972ee2eb42 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 6 Apr 2023 18:05:48 -0400 Subject: [PATCH 12/39] ExtrinsicRune cleanup --- fluent/ExtrinsicRune.ts | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/fluent/ExtrinsicRune.ts b/fluent/ExtrinsicRune.ts index 8863fd86e..1cd508c9e 100644 --- a/fluent/ExtrinsicRune.ts +++ b/fluent/ExtrinsicRune.ts @@ -31,24 +31,20 @@ export type SignatureDataFactory = ( export class ExtrinsicRune extends PatternRune, C, U> { static readonly PROTOCOL_VERSION = 4 - hash = this.chain - .into(ValueRune) - .access("metadata", "extrinsic", "call") - .map((x) => blake2_256.$hash(x)) - .into(CodecRune) - .encoded(this) + $call = this.chain.into(ValueRune).access("metadata", "extrinsic", "call").into(CodecRune) + call = this.$call.encoded(this) - call = this.chain - .into(ValueRune) - .access("metadata", "extrinsic", "call") - .into(CodecRune) - .encoded(this) + $callHash = this.$call.into(ValueRune).map((x) => blake2_256.$hash(x)).into(CodecRune) + hash = this.$callHash.encoded(this) + + $extrinsic = Rune.fn($extrinsic).call(this.chain.metadata).into(CodecRune) + extrinsic = this.$extrinsic.encoded(Rune.object({ + protocolVersion: 4, + call: this, + })) signed(signatureFactory: SignatureDataFactory) { - return Rune - .fn($extrinsic) - .call(this.chain.metadata) - .into(CodecRune) + return this.$extrinsic .encoded(Rune.object({ protocolVersion: 4, call: this, @@ -69,10 +65,9 @@ export class ExtrinsicRune extends PatternRune $.u32.encode(n))) + .call(this.extrinsic, this.extrinsic.access("length").map((n) => $.u32.encode(n))) .map(hex.encodePrefixed) const data = this.chain.connection .call("state_call", "TransactionPaymentApi_query_info", arg) From 842ce0a998ff3a0825096969702788a82b259459 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 6 Apr 2023 18:06:24 -0400 Subject: [PATCH 13/39] ExtrinsicRune cleanup --- examples/multisig/basic.eg.ts | 6 +++--- fluent/ExtrinsicRune.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/multisig/basic.eg.ts b/examples/multisig/basic.eg.ts index f0633bce4..3fa9ceb0f 100644 --- a/examples/multisig/basic.eg.ts +++ b/examples/multisig/basic.eg.ts @@ -57,13 +57,13 @@ await multisig .run() // Check whether the call has been proposed. -const isProposed = await multisig.isProposed(call.hash).run() +const isProposed = await multisig.isProposed(call.callHash).run() console.log("Is proposed:", isProposed) assert(isProposed) // Approve proposal as Billy. await multisig // TODO: get `ratify` working in place of `approve` - .approve({ callHash: call.hash, sender: billy.address }) + .approve({ callHash: call.callHash, sender: billy.address }) .signed(signature({ sender: billy })) .sent() .dbgStatus("First approval:") @@ -71,7 +71,7 @@ await multisig // TODO: get `ratify` working in place of `approve` .run() const { approvals } = await multisig - .proposal(call.hash) + .proposal(call.callHash) .unhandle(undefined) .run() diff --git a/fluent/ExtrinsicRune.ts b/fluent/ExtrinsicRune.ts index 1cd508c9e..ff64364ad 100644 --- a/fluent/ExtrinsicRune.ts +++ b/fluent/ExtrinsicRune.ts @@ -35,7 +35,7 @@ export class ExtrinsicRune extends PatternRune blake2_256.$hash(x)).into(CodecRune) - hash = this.$callHash.encoded(this) + callHash = this.$callHash.encoded(this) $extrinsic = Rune.fn($extrinsic).call(this.chain.metadata).into(CodecRune) extrinsic = this.$extrinsic.encoded(Rune.object({ From b7c02989aac184f4ace2d6089799c2d501649c9b Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 6 Apr 2023 18:46:51 -0400 Subject: [PATCH 14/39] resolve conflicts --- .trunignore | 2 +- patterns/multisig/MultisigRune.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunignore b/.trunignore index fab2a1f2d..19792fbaf 100644 --- a/.trunignore +++ b/.trunignore @@ -1,3 +1,3 @@ +examples/xcm/*.eg.ts examples/ink/*.eg.ts examples/nfts -examples/xcm/asset_teleportation.eg.ts diff --git a/patterns/multisig/MultisigRune.ts b/patterns/multisig/MultisigRune.ts index ef79be036..df311480e 100644 --- a/patterns/multisig/MultisigRune.ts +++ b/patterns/multisig/MultisigRune.ts @@ -54,7 +54,7 @@ export class MultisigRune extends PatternRune>(), From c23796c2d4baef0ca5cf7761a6bbdf6338b9098a Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Mon, 10 Apr 2023 13:45:15 -0300 Subject: [PATCH 15/39] feat: add rococo-dev-xcm network --- capi.config.ts | 17 +++++++++++++++++ devnets/startNetwork.ts | 5 ++++- examples/xcm/reserve_transfer.eg.ts | 6 +++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/capi.config.ts b/capi.config.ts index 1194cb884..0e23c8de5 100644 --- a/capi.config.ts +++ b/capi.config.ts @@ -3,6 +3,7 @@ import { binary, CapiConfig } from "./mod.ts" const polkadot = binary("polkadot", "v0.9.38") const polkadotParachain = binary("polkadot-parachain", "v0.9.380") const substrateContractsNode = binary("substrate-contracts-node", "v0.24.0") +const trappistParachain = binary("trappist-collator", "22755b2") export const config: CapiConfig = { server: "http://localhost:4646/", @@ -58,5 +59,21 @@ export const config: CapiConfig = { }, }, }, + rococoDevXcm: { + binary: polkadot, + chain: "rococo-local", + parachains: { + statemine: { + id: 1000, + binary: polkadotParachain, + chain: "statemine-local", + }, + trappist: { + id: 2000, + binary: trappistParachain, + chain: "local", + }, + }, + }, }, } diff --git a/devnets/startNetwork.ts b/devnets/startNetwork.ts index 84f8a273d..fce190cff 100644 --- a/devnets/startNetwork.ts +++ b/devnets/startNetwork.ts @@ -74,6 +74,7 @@ export async function startNetwork( relaySpec, config.nodes ?? minValidators, [], + relayBinary, signal, ) return { @@ -95,6 +96,7 @@ export async function startNetwork( "--bootnodes", relay.bootnodes, ], + relayBinary, signal, ) return [name, chain] satisfies Narrow @@ -147,6 +149,7 @@ async function spawnChain( chain: string, count: number, extraArgs: string[], + generateNodeKeyBinary: string, signal: AbortSignal, ): Promise { let bootnodes: string | undefined @@ -175,7 +178,7 @@ async function spawnChain( if (bootnodes) { args.push("--bootnodes", bootnodes) } else { - const { nodeKey, peerId } = await generateNodeKey(binary) + const { nodeKey, peerId } = await generateNodeKey(generateNodeKeyBinary) args.push("--node-key", nodeKey) bootnodes = generateBootnodeString(httpPort, peerId) } diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index 632a952ac..f9f4726a5 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -6,11 +6,11 @@ * the exchange. */ +import * as Rococo from "@capi/rococo-dev-xcm/mod.js" +import * as Statemine from "@capi/rococo-dev-xcm/statemine/mod.js" +import * as Trappist from "@capi/rococo-dev-xcm/trappist/mod.js" import { alice, bob, hex, Rune, ValueRune } from "capi" import { signature } from "capi/patterns/signature/statemint.ts" -import * as Rococo from "zombienet/trappist.toml/alice/@latest/mod.js" -import * as Statemine from "zombienet/trappist.toml/statemine-collator01/@latest/mod.js" -import * as Trappist from "zombienet/trappist.toml/trappist-collator01/@latest/mod.js" import { waitFor } from "../../util/mod.ts" // Define some constants for later use. From 7b81406f9db98cf1de88d749dbbd87ded3c9e4a5 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Mon, 10 Apr 2023 14:55:35 -0300 Subject: [PATCH 16/39] feat: add xcm hrmp channels to devnets --- capi.config.ts | 7 +++---- devnets/chainSpec.ts | 8 ++++++++ devnets/startNetwork.ts | 13 +++++++++++++ import_map.json | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/capi.config.ts b/capi.config.ts index 0e23c8de5..bc9118658 100644 --- a/capi.config.ts +++ b/capi.config.ts @@ -3,7 +3,6 @@ import { binary, CapiConfig } from "./mod.ts" const polkadot = binary("polkadot", "v0.9.38") const polkadotParachain = binary("polkadot-parachain", "v0.9.380") const substrateContractsNode = binary("substrate-contracts-node", "v0.24.0") -const trappistParachain = binary("trappist-collator", "22755b2") export const config: CapiConfig = { server: "http://localhost:4646/", @@ -60,17 +59,17 @@ export const config: CapiConfig = { }, }, rococoDevXcm: { - binary: polkadot, + binary: binary("polkadot", "v0.9.37"), chain: "rococo-local", parachains: { statemine: { id: 1000, - binary: polkadotParachain, + binary: binary("polkadot-parachain", "v0.9.370"), chain: "statemine-local", }, trappist: { id: 2000, - binary: trappistParachain, + binary: binary("trappist-collator", "22755b2"), chain: "local", }, }, diff --git a/devnets/chainSpec.ts b/devnets/chainSpec.ts index 48b233b8e..37de44d02 100644 --- a/devnets/chainSpec.ts +++ b/devnets/chainSpec.ts @@ -79,4 +79,12 @@ export interface GenesisConfig { session?: { keys: [account: string, account: string, key: SessionKey][] } + hrmp?: { + preopenHrmpChannels: [ + senderParaId: number, + recipientParaId: number, + maxCapacity: number, + maxMessageSize: number, + ][] + } } diff --git a/devnets/startNetwork.ts b/devnets/startNetwork.ts index fce190cff..9cf6f63ed 100644 --- a/devnets/startNetwork.ts +++ b/devnets/startNetwork.ts @@ -63,6 +63,7 @@ export async function startNetwork( genesisConfig.paras.paras.push( ...paras.map(({ id, genesis }) => [id, [...genesis, true]] satisfies Narrow), ) + addXcmHrmpChannels(genesisConfig, paras.map(({ id }) => id)) } addAuthorities(genesisConfig, minValidators) addTestUsers(genesisConfig.balances.balances) @@ -291,3 +292,15 @@ function addAuthorities(genesisConfig: GenesisConfig, count: number) { ]) ) } + +function addXcmHrmpChannels(genesisConfig: GenesisConfig, paraIds: number[]) { + if (!genesisConfig.hrmp) { + genesisConfig.hrmp = { preopenHrmpChannels: [] } + } + for (const senderParaId of paraIds) { + for (const recipientParaId of paraIds) { + if (senderParaId === recipientParaId) continue + genesisConfig.hrmp.preopenHrmpChannels.push([senderParaId, recipientParaId, 8, 512]) + } + } +} diff --git a/import_map.json b/import_map.json index f658f39db..78b40ad3f 100644 --- a/import_map.json +++ b/import_map.json @@ -12,4 +12,4 @@ "http://localhost:4646/capi/": "./" } } -} +} \ No newline at end of file From cd749d591c374aa9f8ba64327ff5392c4b4e1c61 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Mon, 10 Apr 2023 14:56:18 -0300 Subject: [PATCH 17/39] chore: remove zombienets/ --- zombienets/trappist.toml | 60 ---------------------------------------- 1 file changed, 60 deletions(-) delete mode 100644 zombienets/trappist.toml diff --git a/zombienets/trappist.toml b/zombienets/trappist.toml deleted file mode 100644 index 2079462ed..000000000 --- a/zombienets/trappist.toml +++ /dev/null @@ -1,60 +0,0 @@ -[settings] -timeout = 1000 - -[relaychain] -default_image = "docker.io/paritypr/polkadot-debug:master" -default_command = "`deno task capi bin polkadot v0.9.37`" -chain = "rococo-local" - -[[relaychain.nodes]] -name = "alice" -validator = true -extra_args = ["-lparachain=debug"] - -[[relaychain.nodes]] -name = "bob" -validator = true -extra_args = ["-lparachain=debug"] - -[[relaychain.nodes]] -name = "charlie" -validator = true -extra_args = ["-lparachain=debug"] - -[[parachains]] -id = 1000 -cumulus_based = true -chain = "statemine-local" - -[[parachains.collators]] -name = "statemine-collator01" -image = "docker.io/parity/polkadot-parachain:latest" -command = "`deno task capi bin polkadot-parachain v0.9.370`" -args = ["--log=xcm=trace,pallet-assets=trace"] - -[[parachains]] -id = 2000 -cumulus_based = true - -[[parachains.collators]] -name = "trappist-collator01" -image = "docker.io/parity/polkadot-parachain:latest" -command = "`deno task capi bin trappist-collator 22755b2`" -args = ["--log=xcm=trace,pallet-assets=trace"] - -[types.Header] -number = "u64" -parent_hash = "Hash" -post_state = "Hash" - -[[hrmp_channels]] -sender = 1000 -recipient = 2000 -max_capacity = 8 -max_message_size = 512 - -[[hrmp_channels]] -sender = 2000 -recipient = 1000 -max_capacity = 8 -max_message_size = 512 From 285233a2014236040aad2ef1e0eb7a9daad39a7d Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Mon, 10 Apr 2023 15:01:33 -0300 Subject: [PATCH 18/39] chore: update words.txt --- words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/words.txt b/words.txt index 5b0decbbc..16826845b 100644 --- a/words.txt +++ b/words.txt @@ -60,6 +60,7 @@ paritytech pendings polkadot precommits +preopen prevotes procps relaychain From aba098a1e4d0e58f61b469b11be6dc8d924256e7 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 11 Apr 2023 10:05:32 -0300 Subject: [PATCH 19/39] feat: update reserve asset example types --- examples/xcm/reserve_transfer.eg.ts | 55 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index f9f4726a5..911a84a73 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -6,9 +6,9 @@ * the exchange. */ -import * as Rococo from "@capi/rococo-dev-xcm/mod.js" -import * as Statemine from "@capi/rococo-dev-xcm/statemine/mod.js" -import * as Trappist from "@capi/rococo-dev-xcm/trappist/mod.js" +import * as Rococo from "@capi/rococo-dev-xcm" +import * as Statemine from "@capi/rococo-dev-xcm/statemine" +import * as Trappist from "@capi/rococo-dev-xcm/trappist" import { alice, bob, hex, Rune, ValueRune } from "capi" import { signature } from "capi/patterns/signature/statemint.ts" import { waitFor } from "../../util/mod.ts" @@ -28,7 +28,7 @@ await Rococo.Sudo xcm: Rune.rec({ type: "V2", value: Rune.tuple([ - Rococo.types.xcm.v2.Instruction.Transact({ + Rococo.Instruction.Transact({ originType: "Superuser", requireWeightAtMost: 1000000000n, call: Rune.rec({ @@ -97,17 +97,17 @@ await Trappist.Sudo // Register Trappist parachain asset id to reserve asset id. { - const { v1: { junction: { Junction }, multilocation } } = Trappist.types.xcm + const { Junctions, XcmV1Junction } = Trappist await Trappist.Sudo .sudo({ call: Trappist.AssetRegistry.registerReserveAsset({ assetId: TRAPPIST_ASSET_ID, assetMultiLocation: Rune.rec({ parents: 1, - interior: multilocation.Junctions.X3( - Junction.Parachain(RESERVE_CHAIN_ID), - Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), - Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), + interior: Junctions.X3( + XcmV1Junction.Parachain(RESERVE_CHAIN_ID), + XcmV1Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), + XcmV1Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), ), }), }), @@ -122,32 +122,29 @@ await Trappist.Sudo // Reserve transfer asset id on reserve parachain to Trappist parachain. { const { - xcm: { - VersionedMultiLocation, - VersionedMultiAssets, - v0: { junction: { NetworkId } }, - v1: { - junction: { Junction }, - multilocation: { Junctions }, - multiasset: { AssetId, Fungibility }, - }, - v2: { WeightLimit }, - }, - statemine_runtime: { RuntimeEvent }, - cumulus_pallet_xcmp_queue: { pallet: { Event } }, - } = Statemine.types + VersionedMultiLocation, + VersionedMultiAssets, + Fungibility, + XcmV1Junction, + Junctions, + AssetId, + NetworkId, + WeightLimit, + RuntimeEvent, + CumulusPalletXcmpQueueEvent: { isXcmpMessageSent }, + } = Statemine await Statemine.PolkadotXcm .limitedReserveTransferAssets({ dest: VersionedMultiLocation.V1(Rune.rec({ parents: 1, interior: Junctions.X1( - Junction.Parachain(TRAPPIST_CHAIN_ID), + XcmV1Junction.Parachain(TRAPPIST_CHAIN_ID), ), })), beneficiary: VersionedMultiLocation.V1(Rune.rec({ parents: 0, interior: Junctions.X1( - Junction.AccountId32({ + XcmV1Junction.AccountId32({ network: NetworkId.Any(), id: bob.publicKey, }), @@ -157,8 +154,8 @@ await Trappist.Sudo id: AssetId.Concrete(Rune.rec({ parents: 0, interior: Junctions.X2( - Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), - Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), + XcmV1Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), + XcmV1Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), ), })), fun: Fungibility.Fungible(10000000000000n), @@ -173,9 +170,9 @@ await Trappist.Sudo .into(ValueRune) .map((events) => { const event = events - .find((e) => RuntimeEvent.isXcmpQueue(e.event) && Event.isXcmpMessageSent(e.event.value)) + .find((e) => RuntimeEvent.isXcmpQueue(e.event) && isXcmpMessageSent(e.event.value)) ?.event.value as - | Statemine.types.cumulus_pallet_xcmp_queue.pallet.Event.XcmpMessageSent + | Statemine.CumulusPalletXcmpQueueEvent.XcmpMessageSent | undefined return event?.messageHash ? hex.encode(event.messageHash) : event }) From 2cf0ec4c9bc932a07a371ccd9458ae9cc8d111d5 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 11 Apr 2023 11:22:38 -0300 Subject: [PATCH 20/39] feat: update trappist-collator version --- capi.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capi.config.ts b/capi.config.ts index bc9118658..dd0d48a2c 100644 --- a/capi.config.ts +++ b/capi.config.ts @@ -69,7 +69,7 @@ export const config: CapiConfig = { }, trappist: { id: 2000, - binary: binary("trappist-collator", "22755b2"), + binary: binary("trappist-collator", "79bba6e"), chain: "local", }, }, From e70a6a56913575ae5d527d5c501816d1d2fbc94f Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 11 Apr 2023 14:39:26 -0300 Subject: [PATCH 21/39] Apply suggestions from code review Co-authored-by: T6 --- devnets/startNetwork.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/devnets/startNetwork.ts b/devnets/startNetwork.ts index 9cf6f63ed..aefab40d9 100644 --- a/devnets/startNetwork.ts +++ b/devnets/startNetwork.ts @@ -294,9 +294,7 @@ function addAuthorities(genesisConfig: GenesisConfig, count: number) { } function addXcmHrmpChannels(genesisConfig: GenesisConfig, paraIds: number[]) { - if (!genesisConfig.hrmp) { - genesisConfig.hrmp = { preopenHrmpChannels: [] } - } + genesisConfig.hrmp ??= { preopenHrmpChannels: [] } for (const senderParaId of paraIds) { for (const recipientParaId of paraIds) { if (senderParaId === recipientParaId) continue From e8d71af7491e971101eb4039ee54549e2083f014 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 11 Apr 2023 14:50:06 -0300 Subject: [PATCH 22/39] feat: update addXcmHrmpChannels --- devnets/startNetwork.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/devnets/startNetwork.ts b/devnets/startNetwork.ts index aefab40d9..89b814701 100644 --- a/devnets/startNetwork.ts +++ b/devnets/startNetwork.ts @@ -293,12 +293,22 @@ function addAuthorities(genesisConfig: GenesisConfig, count: number) { ) } -function addXcmHrmpChannels(genesisConfig: GenesisConfig, paraIds: number[]) { - genesisConfig.hrmp ??= { preopenHrmpChannels: [] } +function addXcmHrmpChannels( + genesisConfig: GenesisConfig, + paraIds: number[], + channelMaxCapacity = 8, + channelMaxMessageSize = 512, +) { + genesisConfig.hrmp ??= { preopenHrmpChannels: [] } for (const senderParaId of paraIds) { for (const recipientParaId of paraIds) { if (senderParaId === recipientParaId) continue - genesisConfig.hrmp.preopenHrmpChannels.push([senderParaId, recipientParaId, 8, 512]) + genesisConfig.hrmp.preopenHrmpChannels.push([ + senderParaId, + recipientParaId, + channelMaxCapacity, + channelMaxMessageSize, + ]) } } } From c927cf072792b3f50a633a768670456580ef31e1 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 11 Apr 2023 15:37:58 -0300 Subject: [PATCH 23/39] feat: extract hrmpChannelMaxCapacity hrmpChannelMaxMessageSize into constants --- devnets/startNetwork.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devnets/startNetwork.ts b/devnets/startNetwork.ts index 89b814701..80a5c1498 100644 --- a/devnets/startNetwork.ts +++ b/devnets/startNetwork.ts @@ -293,11 +293,11 @@ function addAuthorities(genesisConfig: GenesisConfig, count: number) { ) } +const hrmpChannelMaxCapacity = 8 +const hrmpChannelMaxMessageSize = 512 function addXcmHrmpChannels( genesisConfig: GenesisConfig, paraIds: number[], - channelMaxCapacity = 8, - channelMaxMessageSize = 512, ) { genesisConfig.hrmp ??= { preopenHrmpChannels: [] } for (const senderParaId of paraIds) { @@ -306,8 +306,8 @@ function addXcmHrmpChannels( genesisConfig.hrmp.preopenHrmpChannels.push([ senderParaId, recipientParaId, - channelMaxCapacity, - channelMaxMessageSize, + hrmpChannelMaxCapacity, + hrmpChannelMaxMessageSize, ]) } } From 54cc1952fa8cc44222021fa0b677bff555bd65fa Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Wed, 12 Apr 2023 11:49:05 -0300 Subject: [PATCH 24/39] Update words.txt Co-authored-by: Harry Solovay --- words.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/words.txt b/words.txt index 16826845b..f2bec96f2 100644 --- a/words.txt +++ b/words.txt @@ -92,4 +92,3 @@ vadimcn westend westmint xcmp -zombienet From 40a07b4ad16c7ea4f60e6ac933ad93666f8b5866 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Wed, 12 Apr 2023 11:17:51 -0400 Subject: [PATCH 25/39] pairing with matias --- examples/xcm/reserve_transfer.eg.ts | 151 ++++++++++++++++------------ util/mod.ts | 1 - util/waitFor.ts | 10 -- 3 files changed, 88 insertions(+), 74 deletions(-) delete mode 100644 util/waitFor.ts diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index 911a84a73..fbbbe7f62 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -1,6 +1,6 @@ /** * @title XCM Reserve Asset Transfer - * @stability nearing + * @stability unstable * @description Perform an XCM reserve asset transfer, in which two chains, which * do not trust one another, rely on a third chain to store assets and facilitate * the exchange. @@ -9,9 +9,12 @@ import * as Rococo from "@capi/rococo-dev-xcm" import * as Statemine from "@capi/rococo-dev-xcm/statemine" import * as Trappist from "@capi/rococo-dev-xcm/trappist" -import { alice, bob, hex, Rune, ValueRune } from "capi" +import { assert, assertNotEquals } from "asserts" +import { $, hex, Rune } from "capi" import { signature } from "capi/patterns/signature/statemint.ts" -import { waitFor } from "../../util/mod.ts" +import { retry } from "../../deps/std/async.ts" + +const { alexa, billy } = await Statemine.createUsers() // Define some constants for later use. const RESERVE_ASSET_ID = 1 @@ -19,6 +22,13 @@ const RESERVE_CHAIN_ID = 1000 // Statemine const TRAPPIST_ASSET_ID = RESERVE_ASSET_ID const TRAPPIST_CHAIN_ID = 2000 +// Define some common options to be used along with `retry`, +// which will poll for XCM-resulting changes. +const retryOptions = { + maxAttempts: Infinity, + maxTimeout: 2 * 60 * 1000, +} + // Create a sufficient asset with Sudo. When targeting a common good // parachain, access root instead through the relay chain. await Rococo.Sudo @@ -36,7 +46,7 @@ await Rococo.Sudo id: RESERVE_ASSET_ID, isSufficient: true, minBalance: 1n, - owner: alice.address, + owner: alexa.address, }).call, }), }), @@ -44,40 +54,44 @@ await Rococo.Sudo }), }), }) - .signed(signature({ sender: alice })) + .signed(signature({ sender: alexa })) .sent() .dbgStatus("Rococo(root) > Statemine(root): Create asset") .finalized() .run() -await waitFor(async () => - (await Statemine.Assets.Asset.value(RESERVE_ASSET_ID).run()) !== undefined -) -console.log( - "Statemine: Asset created", - await Statemine.Assets.Asset.value(RESERVE_ASSET_ID).run(), -) +// Wait for the asset to be recorded in storage. +const assetDetails = await retry(() => + Statemine.Assets.Asset + .value(RESERVE_ASSET_ID) + .unhandle(undefined) + .run(), retryOptions) + +// Ensure the reserve asset was created. +console.log("Statemine: Asset created", assetDetails) +$.assert(Statemine.$assetDetails, assetDetails) // Mint assets on reserve parachain. await Statemine.Assets .mint({ id: RESERVE_ASSET_ID, amount: 100000000000000n, - beneficiary: bob.address, + beneficiary: billy.address, }) - .signed(signature({ sender: alice })) + .signed(signature({ sender: alexa })) .sent() - .dbgStatus("Statemine(Alice): Mint reserve asset to Bob") + .dbgStatus("Statemine(Alexa): Mint reserve asset to Billy") .finalized() .run() -const bobStatemintBalance = Statemine.Assets.Account - .value([RESERVE_ASSET_ID, bob.publicKey]) +const billyStatemintBalance = Statemine.Assets.Account + .value([RESERVE_ASSET_ID, billy.publicKey]) .unhandle(undefined) .access("balance") -const bobStatemintBalanceInitial = await bobStatemintBalance.run() -console.log("Statemine(Bob): asset balance", bobStatemintBalanceInitial) +const billyStatemintBalanceInitial = await billyStatemintBalance.run() +console.log("Statemine(Billy): asset balance", billyStatemintBalanceInitial) +$.assert($.u128, billyStatemintBalanceInitial) // Create the asset on the Trappist parachain. await Trappist.Sudo @@ -86,41 +100,39 @@ await Trappist.Sudo id: TRAPPIST_ASSET_ID, isSufficient: false, minBalance: 1n, - owner: alice.address, + owner: alexa.address, }), }) - .signed(signature({ sender: alice })) + .signed(signature({ sender: alexa })) .sent() .dbgStatus("Trappist(root): Create derived asset") .finalized() .run() // Register Trappist parachain asset id to reserve asset id. -{ - const { Junctions, XcmV1Junction } = Trappist - await Trappist.Sudo - .sudo({ - call: Trappist.AssetRegistry.registerReserveAsset({ - assetId: TRAPPIST_ASSET_ID, - assetMultiLocation: Rune.rec({ - parents: 1, - interior: Junctions.X3( - XcmV1Junction.Parachain(RESERVE_CHAIN_ID), - XcmV1Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), - XcmV1Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), - ), - }), +await Trappist.Sudo + .sudo({ + call: Trappist.AssetRegistry.registerReserveAsset({ + assetId: TRAPPIST_ASSET_ID, + assetMultiLocation: Rune.rec({ + parents: 1, + interior: Trappist.Junctions.X3( + Trappist.XcmV1Junction.Parachain(RESERVE_CHAIN_ID), + Trappist.XcmV1Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), + Trappist.XcmV1Junction.GeneralIndex(BigInt(RESERVE_ASSET_ID)), + ), }), - }) - .signed(signature({ sender: alice })) - .sent() - .dbgStatus("Trappist(root): Register AssetId to Reserve AssetId") - .finalized() - .run() -} + }), + }) + .signed(signature({ sender: alexa })) + .sent() + .dbgStatus("Trappist(root): Register AssetId to Reserve AssetId") + .finalized() + .run() // Reserve transfer asset id on reserve parachain to Trappist parachain. { + // Destructure the factories to be used (for convenience). const { VersionedMultiLocation, VersionedMultiAssets, @@ -133,7 +145,7 @@ await Trappist.Sudo RuntimeEvent, CumulusPalletXcmpQueueEvent: { isXcmpMessageSent }, } = Statemine - await Statemine.PolkadotXcm + const events = await Statemine.PolkadotXcm .limitedReserveTransferAssets({ dest: VersionedMultiLocation.V1(Rune.rec({ parents: 1, @@ -146,7 +158,7 @@ await Trappist.Sudo interior: Junctions.X1( XcmV1Junction.AccountId32({ network: NetworkId.Any(), - id: bob.publicKey, + id: billy.publicKey, }), ), })), @@ -163,34 +175,42 @@ await Trappist.Sudo feeAssetItem: 0, weightLimit: WeightLimit.Unlimited(), }) - .signed(signature({ sender: bob })) + .signed(signature({ sender: billy })) .sent() - .dbgStatus("Statemine(Bob): Reserve transfer to Trappist") + .dbgStatus("Statemine(Billy): Reserve transfer to Trappist") .finalizedEvents() - .into(ValueRune) - .map((events) => { - const event = events - .find((e) => RuntimeEvent.isXcmpQueue(e.event) && isXcmpMessageSent(e.event.value)) - ?.event.value as - | Statemine.CumulusPalletXcmpQueueEvent.XcmpMessageSent - | undefined - return event?.messageHash ? hex.encode(event.messageHash) : event - }) - .dbg("XcmpMessageSent.messageHash") .run() + + for (const { event } of events) { + if (RuntimeEvent.isXcmpQueue(event) && isXcmpMessageSent(event.value)) { + console.log("XcmpMessageSent.messageHash:", event.value) + // TODO: assert on specific variant of runtime event value + } + } } -const bobTrappistAssetAccount = Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, bob.publicKey]) +// Retrieve billy's balance on Trappist. +const billyTrappistAssetAccount = Trappist.Assets.Account.value([ + TRAPPIST_ASSET_ID, + billy.publicKey, +]) +const { balance: billyTrappistBalance } = await retry( + () => billyTrappistAssetAccount.unhandle(undefined).run(), + retryOptions, +) -await waitFor(async () => !!await bobTrappistAssetAccount.run()) +// Ensure the balance is greater than zero. +console.log("Trappist(Billy): asset balance:", billyTrappistBalance) +assert(billyTrappistBalance > 0) -const bobTrappistBalance = await bobTrappistAssetAccount - .unhandle(undefined).access("balance").run() -console.log("Trappist(Bob): asset balance:", bobTrappistBalance) +// Retrieve Billy's balance on statemint. +const billyStatemintBalanceFinal = await billyStatemintBalance.run() -const bobStatemintBalanceFinal = await bobStatemintBalance.run() -console.log("Statemine(Bob): asset balance:", bobStatemintBalanceFinal) +// Ensure the balance is different from the initial. +console.log("Statemine(Billy): asset balance:", billyStatemintBalanceFinal) +assertNotEquals(billyStatemintBalanceInitial, billyStatemintBalanceFinal) +// Retrieve the statemint sovereign account balance. const statemintSovereignAccountBalance = await Statemine.Assets.Account // Sovereign address on sibling chain // b"sibl" + $.u32.encode(2000) + 0...0 @@ -198,5 +218,10 @@ const statemintSovereignAccountBalance = await Statemine.Assets.Account RESERVE_ASSET_ID, hex.decode("0x7369626cd0070000000000000000000000000000000000000000000000000000"), ]) + .unhandle(undefined) + .access("balance") .run() + +// Ensure the balance is greater than zero. console.log("Statemine(TrappistSovereignAccount): asset balance", statemintSovereignAccountBalance) +assert(statemintSovereignAccountBalance > 0) diff --git a/util/mod.ts b/util/mod.ts index eaeb77794..e378e49c6 100644 --- a/util/mod.ts +++ b/util/mod.ts @@ -10,5 +10,4 @@ export * from "./memo.ts" export * from "./normalize.ts" export * from "./notifier.ts" export * from "./state.ts" -export * from "./waitFor.ts" export * from "./withSignal.ts" diff --git a/util/waitFor.ts b/util/waitFor.ts deleted file mode 100644 index 620f59c06..000000000 --- a/util/waitFor.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { delay } from "../deps/std/async.ts" - -export async function waitFor(fn: () => Promise, interval = 1000, maxAttempts = 60) { - let attempts = 0 - while (attempts++ < maxAttempts) { - if (await fn()) return - await delay(interval) - } - throw new Error("waitFor maxAttempts reached") -} From f92b29367ef782f983398e8a651f0756d5076b1c Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Wed, 12 Apr 2023 12:53:09 -0300 Subject: [PATCH 26/39] feat: inline billyTrappistBalance --- examples/xcm/reserve_transfer.eg.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index fbbbe7f62..86f02ad1b 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -190,12 +190,9 @@ await Trappist.Sudo } // Retrieve billy's balance on Trappist. -const billyTrappistAssetAccount = Trappist.Assets.Account.value([ - TRAPPIST_ASSET_ID, - billy.publicKey, -]) const { balance: billyTrappistBalance } = await retry( - () => billyTrappistAssetAccount.unhandle(undefined).run(), + () => + Trappist.Assets.Account.value([TRAPPIST_ASSET_ID, billy.publicKey]).unhandle(undefined).run(), retryOptions, ) From 07ca2709c793ee9747db18e033ff4cf250ff0f94 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Wed, 12 Apr 2023 14:32:01 -0300 Subject: [PATCH 27/39] feat: use alice for sudo calls --- examples/xcm/reserve_transfer.eg.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index 86f02ad1b..8cc689db8 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -10,7 +10,7 @@ import * as Rococo from "@capi/rococo-dev-xcm" import * as Statemine from "@capi/rococo-dev-xcm/statemine" import * as Trappist from "@capi/rococo-dev-xcm/trappist" import { assert, assertNotEquals } from "asserts" -import { $, hex, Rune } from "capi" +import { $, alice as root, hex, Rune } from "capi" import { signature } from "capi/patterns/signature/statemint.ts" import { retry } from "../../deps/std/async.ts" @@ -25,6 +25,7 @@ const TRAPPIST_CHAIN_ID = 2000 // Define some common options to be used along with `retry`, // which will poll for XCM-resulting changes. const retryOptions = { + multiplier: 1, maxAttempts: Infinity, maxTimeout: 2 * 60 * 1000, } @@ -54,18 +55,17 @@ await Rococo.Sudo }), }), }) - .signed(signature({ sender: alexa })) + .signed(signature({ sender: root })) .sent() .dbgStatus("Rococo(root) > Statemine(root): Create asset") .finalized() .run() // Wait for the asset to be recorded in storage. -const assetDetails = await retry(() => - Statemine.Assets.Asset - .value(RESERVE_ASSET_ID) - .unhandle(undefined) - .run(), retryOptions) +const assetDetails = await retry( + () => Statemine.Assets.Asset.value(RESERVE_ASSET_ID).unhandle(undefined).run(), + retryOptions, +) // Ensure the reserve asset was created. console.log("Statemine: Asset created", assetDetails) @@ -103,7 +103,7 @@ await Trappist.Sudo owner: alexa.address, }), }) - .signed(signature({ sender: alexa })) + .signed(signature({ sender: root })) .sent() .dbgStatus("Trappist(root): Create derived asset") .finalized() @@ -124,7 +124,7 @@ await Trappist.Sudo }), }), }) - .signed(signature({ sender: alexa })) + .signed(signature({ sender: root })) .sent() .dbgStatus("Trappist(root): Register AssetId to Reserve AssetId") .finalized() @@ -184,7 +184,6 @@ await Trappist.Sudo for (const { event } of events) { if (RuntimeEvent.isXcmpQueue(event) && isXcmpMessageSent(event.value)) { console.log("XcmpMessageSent.messageHash:", event.value) - // TODO: assert on specific variant of runtime event value } } } From 400063c84ae540d410640b5bbda7fecb2097d810 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Wed, 12 Apr 2023 18:22:54 -0300 Subject: [PATCH 28/39] fix: ingnore smoldot example --- .trunignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.trunignore b/.trunignore index 19792fbaf..598051e07 100644 --- a/.trunignore +++ b/.trunignore @@ -1,3 +1,4 @@ examples/xcm/*.eg.ts +examples/smoldot.eg.ts examples/ink/*.eg.ts examples/nfts From 76045d7183dd290b7968e467bba02ae104091ce7 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Fri, 14 Apr 2023 15:29:10 -0300 Subject: [PATCH 29/39] chore: re-enable smoldot example --- .trunignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.trunignore b/.trunignore index 598051e07..19792fbaf 100644 --- a/.trunignore +++ b/.trunignore @@ -1,4 +1,3 @@ examples/xcm/*.eg.ts -examples/smoldot.eg.ts examples/ink/*.eg.ts examples/nfts From 6569aa257be7dbce0743134708e653aae0d32d0f Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Fri, 14 Apr 2023 17:25:19 -0300 Subject: [PATCH 30/39] feat: add sovereign account codec --- examples/xcm/reserve_transfer.eg.ts | 5 +++-- patterns/para_id.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 patterns/para_id.ts diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index 8cc689db8..25772d193 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -10,7 +10,8 @@ import * as Rococo from "@capi/rococo-dev-xcm" import * as Statemine from "@capi/rococo-dev-xcm/statemine" import * as Trappist from "@capi/rococo-dev-xcm/trappist" import { assert, assertNotEquals } from "asserts" -import { $, alice as root, hex, Rune } from "capi" +import { $, alice as root, Rune } from "capi" +import { $siblId } from "capi/patterns/para_id.ts" import { signature } from "capi/patterns/signature/statemint.ts" import { retry } from "../../deps/std/async.ts" @@ -212,7 +213,7 @@ const statemintSovereignAccountBalance = await Statemine.Assets.Account // b"sibl" + $.u32.encode(2000) + 0...0 .value([ RESERVE_ASSET_ID, - hex.decode("0x7369626cd0070000000000000000000000000000000000000000000000000000"), + $siblId.encode(TRAPPIST_CHAIN_ID), ]) .unhandle(undefined) .access("balance") diff --git a/patterns/para_id.ts b/patterns/para_id.ts new file mode 100644 index 000000000..914ea13bd --- /dev/null +++ b/patterns/para_id.ts @@ -0,0 +1,26 @@ +import * as $ from "../deps/scale.ts" + +export const $paraId = sovereignAccountFactory("para") +export const $siblId = sovereignAccountFactory("sibl") + +function sovereignAccountFactory(prefix: "sibl" | "para") { + return $.createCodec({ + _metadata: $.metadata("$sovereignAccount"), + _staticSize: 32, + _encode(buffer, value) { + $.sizedUint8Array(4)._encode(buffer, new TextEncoder().encode(prefix)) + $.u32._encode(buffer, value) + $.sizedUint8Array(24)._encode(buffer, new Uint8Array(24)) + }, + _decode(buffer) { + const prefix_ = new TextDecoder().decode($.sizedUint8Array(4)._decode(buffer)) + const paraId = $.u32._decode(buffer) + buffer.index += 24 + if (prefix != prefix_) throw new Error("Invalid prefix") + return paraId + }, + _assert(assert) { + assert.typeof(this, "number") + }, + }) +} From bd78ffb4035e2a21c59236b7f234377cdcfd1581 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Fri, 14 Apr 2023 18:13:18 -0300 Subject: [PATCH 31/39] chore: remove extra comments --- examples/xcm/reserve_transfer.eg.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index 25772d193..3e576050c 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -209,12 +209,7 @@ assertNotEquals(billyStatemintBalanceInitial, billyStatemintBalanceFinal) // Retrieve the statemint sovereign account balance. const statemintSovereignAccountBalance = await Statemine.Assets.Account - // Sovereign address on sibling chain - // b"sibl" + $.u32.encode(2000) + 0...0 - .value([ - RESERVE_ASSET_ID, - $siblId.encode(TRAPPIST_CHAIN_ID), - ]) + .value([RESERVE_ASSET_ID, $siblId.encode(TRAPPIST_CHAIN_ID)]) .unhandle(undefined) .access("balance") .run() From dcd6f963ea10c18acf613228631c40f86457c069 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Sat, 15 Apr 2023 13:18:22 -0400 Subject: [PATCH 32/39] rebase and use factories --- examples/xcm/reserve_transfer.eg.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index 3e576050c..078d32978 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -37,13 +37,12 @@ await Rococo.Sudo .sudo({ call: Rococo.ParasSudoWrapper.sudoQueueDownwardXcm({ id: RESERVE_CHAIN_ID, - xcm: Rune.rec({ - type: "V2", - value: Rune.tuple([ + xcm: Rococo.VersionedXcm.V2( + Rune.array([ Rococo.Instruction.Transact({ originType: "Superuser", requireWeightAtMost: 1000000000n, - call: Rune.rec({ + call: Rococo.DoubleEncoded({ encoded: Statemine.Assets.forceCreate({ id: RESERVE_ASSET_ID, isSufficient: true, @@ -53,7 +52,7 @@ await Rococo.Sudo }), }), ]), - }), + ), }), }) .signed(signature({ sender: root })) @@ -115,7 +114,7 @@ await Trappist.Sudo .sudo({ call: Trappist.AssetRegistry.registerReserveAsset({ assetId: TRAPPIST_ASSET_ID, - assetMultiLocation: Rune.rec({ + assetMultiLocation: Trappist.XcmV1MultiLocation({ parents: 1, interior: Trappist.Junctions.X3( Trappist.XcmV1Junction.Parachain(RESERVE_CHAIN_ID), @@ -148,13 +147,13 @@ await Trappist.Sudo } = Statemine const events = await Statemine.PolkadotXcm .limitedReserveTransferAssets({ - dest: VersionedMultiLocation.V1(Rune.rec({ + dest: VersionedMultiLocation.V1(Statemine.XcmV1MultiLocation({ parents: 1, interior: Junctions.X1( XcmV1Junction.Parachain(TRAPPIST_CHAIN_ID), ), })), - beneficiary: VersionedMultiLocation.V1(Rune.rec({ + beneficiary: VersionedMultiLocation.V1(Statemine.XcmV1MultiLocation({ parents: 0, interior: Junctions.X1( XcmV1Junction.AccountId32({ @@ -163,8 +162,8 @@ await Trappist.Sudo }), ), })), - assets: VersionedMultiAssets.V1(Rune.array([Rune.rec({ - id: AssetId.Concrete(Rune.rec({ + assets: VersionedMultiAssets.V1(Rune.array([Statemine.XcmV1MultiAsset({ + id: AssetId.Concrete(Statemine.XcmV1MultiLocation({ parents: 0, interior: Junctions.X2( XcmV1Junction.PalletInstance((await Statemine.Assets.pallet.run()).id), From 0de6835392f3ccc26c39ca0d6823cc230f3b9612 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Sat, 15 Apr 2023 13:39:58 -0400 Subject: [PATCH 33/39] rebase and fix type errors --- patterns/signature/statemint.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/patterns/signature/statemint.ts b/patterns/signature/statemint.ts index f221fd3aa..23d0e48f5 100644 --- a/patterns/signature/statemint.ts +++ b/patterns/signature/statemint.ts @@ -49,19 +49,19 @@ export function signature(_props: RunicArgs x ?? Era.Immortal) const tip = Rune.resolve(props.tip).map((x) => x ?? 0n) - return Rune.rec({ + return Rune.object({ sender: props.sender, - extra: Rune.rec({ + extra: Rune.object({ CheckMortality: mortality, CheckNonce: nonce, ChargeTransactionPayment: tip, - ChargeAssetTxPayment: Rune.rec({ + ChargeAssetTxPayment: Rune.object({ // TODO: // assetId: props.assetId, tip: tip, }), }), - additional: Rune.rec({ + additional: Rune.object({ CheckSpecVersion: specVersion, CheckTxVersion: transactionVersion, CheckGenesis: genesisHash, From 0d2e6abebc160a8d5503a48846fc5ad06a8593b8 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Mon, 17 Apr 2023 13:00:01 -0300 Subject: [PATCH 34/39] feat: update import_map.json --- import_map.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/import_map.json b/import_map.json index 78b40ad3f..389f65eba 100644 --- a/import_map.json +++ b/import_map.json @@ -1,6 +1,6 @@ { "imports": { - "@capi/": "http://localhost:4646/15e2f7fb3ffb651e/" + "@capi/": "http://localhost:4646/29dcd8564faca56b/" }, "scopes": { "examples/": { @@ -12,4 +12,4 @@ "http://localhost:4646/capi/": "./" } } -} \ No newline at end of file +} From bcb5b6dd398302232556c10b9025d95f6618a561 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Mon, 17 Apr 2023 13:50:28 -0300 Subject: [PATCH 35/39] revert: patterns/signature/polkadot.ts --- patterns/signature/polkadot.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/patterns/signature/polkadot.ts b/patterns/signature/polkadot.ts index ece29ac5d..25c4fac79 100644 --- a/patterns/signature/polkadot.ts +++ b/patterns/signature/polkadot.ts @@ -5,8 +5,8 @@ import { $, hex, ss58, ValueRune } from "../../mod.ts" import { Rune, RunicArgs } from "../../rune/Rune.ts" import { Era } from "../../scale_info/overrides/Era.ts" -export interface SignatureProps { - sender: ExtrinsicSender +export interface SignatureProps { + sender: ExtrinsicSender checkpoint?: string mortality?: Era nonce?: number @@ -28,7 +28,7 @@ export interface PolkadotSignatureChain extends AddressPrefixChain { } } -export function signature(_props: RunicArgs) { +export function signature(_props: RunicArgs>) { return (chain: ChainRune) => { const props = RunicArgs.resolve(_props) const addrPrefix = chain.addressPrefix() From 4b4b150483e31947ca02e564bc9cd539c0d390d0 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 18 Apr 2023 11:46:04 -0300 Subject: [PATCH 36/39] Apply suggestions from code review Co-authored-by: T6 --- fluent/ExtrinsicRune.ts | 2 +- patterns/para_id.ts | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/fluent/ExtrinsicRune.ts b/fluent/ExtrinsicRune.ts index ff64364ad..62c8f87a9 100644 --- a/fluent/ExtrinsicRune.ts +++ b/fluent/ExtrinsicRune.ts @@ -32,7 +32,7 @@ export class ExtrinsicRune extends PatternRune blake2_256.$hash(x)).into(CodecRune) callHash = this.$callHash.encoded(this) diff --git a/patterns/para_id.ts b/patterns/para_id.ts index 914ea13bd..70a06cca9 100644 --- a/patterns/para_id.ts +++ b/patterns/para_id.ts @@ -4,23 +4,25 @@ export const $paraId = sovereignAccountFactory("para") export const $siblId = sovereignAccountFactory("sibl") function sovereignAccountFactory(prefix: "sibl" | "para") { + const $prefix = $.constant(null, new TextEncoder().encode(prefix)) + const $postfix = $.constant(null, new Uint8Array(24)) return $.createCodec({ - _metadata: $.metadata("$sovereignAccount"), - _staticSize: 32, + _metadata: $.metadata(`$${prefix}Id`), + _staticSize: $prefix._staticSize + $.u32._staticSize + $postfix._staticSize, _encode(buffer, value) { - $.sizedUint8Array(4)._encode(buffer, new TextEncoder().encode(prefix)) - $.u32._encode(buffer, value) - $.sizedUint8Array(24)._encode(buffer, new Uint8Array(24)) + $prefix._encode(buffer, null) + $.u32.encode(buffer, value) + $postfix._encode(buffer, null) }, _decode(buffer) { - const prefix_ = new TextDecoder().decode($.sizedUint8Array(4)._decode(buffer)) + $prefix._decode(buffer) const paraId = $.u32._decode(buffer) - buffer.index += 24 - if (prefix != prefix_) throw new Error("Invalid prefix") + $postfix._decode(buffer) return paraId }, _assert(assert) { - assert.typeof(this, "number") + $.u32._assert(assert) }, }) } +} From 7938a4f0ab713b7358330f4ec23b8c427bed5c8b Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 18 Apr 2023 11:57:22 -0300 Subject: [PATCH 37/39] fix: GH commit suggestions --- examples/xcm/reserve_transfer.eg.ts | 2 +- patterns/para_id.ts | 3 +-- patterns/signature/statemint.ts | 12 +----------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index 078d32978..a95af5479 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -48,7 +48,7 @@ await Rococo.Sudo isSufficient: true, minBalance: 1n, owner: alexa.address, - }).call, + }).callData, }), }), ]), diff --git a/patterns/para_id.ts b/patterns/para_id.ts index 70a06cca9..66f707b11 100644 --- a/patterns/para_id.ts +++ b/patterns/para_id.ts @@ -11,7 +11,7 @@ function sovereignAccountFactory(prefix: "sibl" | "para") { _staticSize: $prefix._staticSize + $.u32._staticSize + $postfix._staticSize, _encode(buffer, value) { $prefix._encode(buffer, null) - $.u32.encode(buffer, value) + $.u32._encode(buffer, value) $postfix._encode(buffer, null) }, _decode(buffer) { @@ -25,4 +25,3 @@ function sovereignAccountFactory(prefix: "sibl" | "para") { }, }) } -} diff --git a/patterns/signature/statemint.ts b/patterns/signature/statemint.ts index 23d0e48f5..9ae499d52 100644 --- a/patterns/signature/statemint.ts +++ b/patterns/signature/statemint.ts @@ -2,7 +2,6 @@ import { Chain, ChainRune, Era, - ExtrinsicSender, hex, Rune, RunicArgs, @@ -10,14 +9,7 @@ import { ss58, ValueRune, } from "../../mod.ts" - -export interface SignatureProps { - sender: ExtrinsicSender - checkpoint?: string - mortality?: Era - nonce?: number - tip?: bigint -} +import { SignatureProps } from "../signature/polkadot.ts" export function signature(_props: RunicArgs>) { return (chain: ChainRune) => { @@ -56,8 +48,6 @@ export function signature(_props: RunicArgs Date: Tue, 18 Apr 2023 12:30:37 -0300 Subject: [PATCH 38/39] feat: remove signature/westmint.ts --- examples/nfts.eg.ts | 2 +- patterns/signature/statemint.ts | 7 +++- patterns/signature/westmint.ts | 58 --------------------------------- 3 files changed, 7 insertions(+), 60 deletions(-) delete mode 100644 patterns/signature/westmint.ts diff --git a/examples/nfts.eg.ts b/examples/nfts.eg.ts index 42dcb5938..ba8a1d365 100644 --- a/examples/nfts.eg.ts +++ b/examples/nfts.eg.ts @@ -17,7 +17,7 @@ import { import { assertEquals } from "asserts" import { $, Rune } from "capi" import { DefaultCollectionSetting, DefaultItemSetting } from "capi/patterns/nfts.ts" -import { signature } from "capi/patterns/signature/westmint.ts" +import { signature } from "capi/patterns/signature/statemint.ts" // Create two test users. Alexa will mint and list the NFT. Billy will purchase it. const { alexa, billy } = await createUsers() diff --git a/patterns/signature/statemint.ts b/patterns/signature/statemint.ts index 9ae499d52..315fb86fd 100644 --- a/patterns/signature/statemint.ts +++ b/patterns/signature/statemint.ts @@ -11,7 +11,11 @@ import { } from "../../mod.ts" import { SignatureProps } from "../signature/polkadot.ts" -export function signature(_props: RunicArgs>) { +type StatemintSignatureProps = SignatureProps & { + assetId?: number +} + +export function signature(_props: RunicArgs>) { return (chain: ChainRune) => { const props = RunicArgs.resolve(_props) const addrPrefix = chain.addressPrefix() @@ -48,6 +52,7 @@ export function signature(_props: RunicArgs & { - assetId?: number -} - -export function signature(_props: RunicArgs) { - return (chain: ChainRune) => { - const props = RunicArgs.resolve(_props) - const addrPrefix = chain.addressPrefix() - const versions = chain.pallet("System").constant("Version").decoded - const specVersion = versions.access("specVersion") - const transactionVersion = versions.access("transactionVersion") - // TODO: create union rune (with `matchTag` method) and utilize here - // TODO: MultiAddress conversion utils - const senderSs58 = Rune - .tuple([addrPrefix, props.sender]) - .map(([addrPrefix, sender]) => { - switch (sender.address.type) { - case "Id": - return ss58.encode(addrPrefix, sender.address.value) - default: - throw new Error("unimplemented") - } - }) - .throws(ss58.InvalidPayloadLengthError) - const nonce = Rune.resolve(props.nonce) - .unhandle(undefined) - .rehandle(undefined, () => chain.connection.call("system_accountNextIndex", senderSs58)) - const genesisHashHex = chain.connection.call("chain_getBlockHash", 0).unsafeAs() - .into(ValueRune) - const genesisHash = genesisHashHex.map(hex.decode) - const checkpointHash = Rune.tuple([props.checkpoint, genesisHashHex]).map(([a, b]) => a ?? b) - .map(hex.decode) - const mortality = Rune.resolve(props.mortality).map((x) => x ?? Era.Immortal) - const tip = Rune.resolve(props.tip).map((x) => x ?? 0n) - return Rune.object({ - sender: props.sender, - extra: Rune.object({ - CheckMortality: mortality, - CheckNonce: nonce, - ChargeTransactionPayment: tip, - ChargeAssetTxPayment: Rune.object({ - assetId: props.assetId, - tip: tip, - }), - }), - additional: Rune.object({ - CheckSpecVersion: specVersion, - CheckTxVersion: transactionVersion, - CheckGenesis: genesisHash, - CheckMortality: checkpointHash, - }), - }) satisfies Rune, unknown> - } -} From 75785f90785ea9d600f64f2abca97a6c1cf55155 Mon Sep 17 00:00:00 2001 From: Matias Volpe Date: Tue, 18 Apr 2023 12:31:24 -0300 Subject: [PATCH 39/39] feat: rename $call to $callData --- fluent/ExtrinsicRune.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fluent/ExtrinsicRune.ts b/fluent/ExtrinsicRune.ts index 62c8f87a9..1365bb858 100644 --- a/fluent/ExtrinsicRune.ts +++ b/fluent/ExtrinsicRune.ts @@ -31,10 +31,10 @@ export type SignatureDataFactory = ( export class ExtrinsicRune extends PatternRune, C, U> { static readonly PROTOCOL_VERSION = 4 - $call = this.chain.into(ValueRune).access("metadata", "extrinsic", "call").into(CodecRune) - callData = this.$call.encoded(this) + $callData = this.chain.into(ValueRune).access("metadata", "extrinsic", "call").into(CodecRune) + callData = this.$callData.encoded(this) - $callHash = this.$call.into(ValueRune).map((x) => blake2_256.$hash(x)).into(CodecRune) + $callHash = this.$callData.into(ValueRune).map((x) => blake2_256.$hash(x)).into(CodecRune) callHash = this.$callHash.encoded(this) $extrinsic = Rune.fn($extrinsic).call(this.chain.metadata).into(CodecRune)