Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
feat: add xcm_reserve_transfer example
Browse files Browse the repository at this point in the history
  • Loading branch information
kratico committed Mar 21, 2023
1 parent 3a31906 commit fb6d32a
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 39 deletions.
284 changes: 249 additions & 35 deletions examples/xcm_reserve_transfer.ts
Original file line number Diff line number Diff line change
@@ -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<t.xcm.v2.Instruction>
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<boolean>,
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<X, C extends Chain>(_props: RunicArgs<X, SignatureProps<C>>) {
return <CU>(chain: ChainRune<C, CU>) => {
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<string>()
.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<SignatureData<C>, unknown>
}
}
6 changes: 6 additions & 0 deletions fluent/ExtrinsicRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type SignatureDataFactory<C extends Chain, CU, SU> = (

export class ExtrinsicRune<out C extends Chain, out U> extends Rune<Chain.Call<C>, U> {
hash
call

constructor(_prime: ExtrinsicRune<C, U>["_prime"], readonly chain: ChainRune<C, U>) {
super(_prime)
Expand All @@ -31,6 +32,11 @@ export class ExtrinsicRune<out C extends Chain, out U> extends Rune<Chain.Call<C
.map((x) => blake2_256.$hash<any>(x))
.into(CodecRune)
.encoded(this)
this.call = this.chain
.into(ValueRune)
.access("metadata", "extrinsic", "call")
.into(CodecRune)
.encoded(this)
}

signed<SU>(signatureFactory: SignatureDataFactory<C, U, SU>) {
Expand Down
6 changes: 3 additions & 3 deletions patterns/signature/polkadot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PolkadotSignatureChain>
export interface SignatureProps<T extends Chain> {
sender: ExtrinsicSender<T>
checkpoint?: string
mortality?: Era
nonce?: number
Expand All @@ -28,7 +28,7 @@ export interface PolkadotSignatureChain extends AddressPrefixChain {
}
}

export function signature<X>(_props: RunicArgs<X, SignatureProps>) {
export function signature<X>(_props: RunicArgs<X, SignatureProps<PolkadotSignatureChain>>) {
return <CU>(chain: ChainRune<PolkadotSignatureChain, CU>) => {
const props = RunicArgs.resolve(_props)
const addrPrefix = chain.addressPrefix()
Expand Down
6 changes: 5 additions & 1 deletion rpc/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export class WsConnection extends Connection {
}

close() {
this.ws.close()
console.log("will close", this.ws.readyState, this.url)
// console.trace()
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.close()
}
}
}
8 changes: 8 additions & 0 deletions scale_info/overrides/overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export const overrides: Record<string, (ty: Ty, visit: (i: number) => Codec<any>
"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
},
Expand Down
2 changes: 2 additions & 0 deletions words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ hashbrown
hasher
hashers
heiko
hrmp
hydradx
idents
inherents
Expand Down Expand Up @@ -223,6 +224,7 @@ valiu
wasmbuild
westend
westmint
xcmp
xxhash
xxnetwork
zio
Expand Down

0 comments on commit fb6d32a

Please sign in to comment.