From 137320b42c82010ce5b96ca4176596591e2172c5 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Tue, 13 Feb 2024 11:40:21 +0000 Subject: [PATCH] Merge pull request #1050 from UniqueNetwork/feat/hrmp-channels-proposals-script --- --- a/js-packages/scripts/councilproposal.ts +++ b/js-packages/scripts/councilproposal.ts @@ -2,6 +2,11 @@ import {blake2AsHex} from '@polkadot/util-crypto'; async function main() { + if(process.argv.length != 4) { + console.log('Usage: yarn councilDemocracyProposal '); + process.exit(1); + } + const networkUrl = process.argv[2]; const democracyProposalContent = process.argv[3]; @@ -24,12 +29,12 @@ democracyProposal.method.encodedLength, ).method.toHex(); - const proposeUpgradeBatch = api.tx.utility.batchAll([ + const proposeBatch = api.tx.utility.batchAll([ democracyProposalPreimage, councilProposal, ]); - const encodedCall = proposeUpgradeBatch.method.toHex(); + const encodedCall = proposeBatch.method.toHex(); console.log('-----------------'); console.log('Council Proposal: ', `https://polkadot.js.org/apps/?rpc=${networkUrl}#/extrinsics/decode/${encodedCall}`); --- /dev/null +++ b/js-packages/scripts/hrmpchannel.ts @@ -0,0 +1,214 @@ +import {ApiPromise, WsProvider} from '@polkadot/api'; +import {blake2AsHex, encodeAddress} from '@polkadot/util-crypto'; + +export const paraChildSovereignAccount = (relayApi: ApiPromise, paraid: number) => { + // We are getting a *child* parachain sovereign account, + // so we need a child prefix: encoded(b"para") == 0x70617261 + const childPrefix = '70617261'; + + const encodedParaId = relayApi + .createType('u32', paraid) + .toHex(true) + .substring(2 /* skip 0x */); + + const addrBytesLen = 32; + const byteLenInHex = 2; + return '0x' + (childPrefix + encodedParaId).padEnd(addrBytesLen * byteLenInHex, '0'); +}; + +const proposeOpenChannel = async (relayApi: ApiPromise, relayFee: bigint, senderParaId: number, receiverParaId: number, skipBalanceCheck: boolean) => { + const conf: any = await relayApi.query.configuration.activeConfig() + .then(data => data.toJSON()); + const maxCapacity = conf.hrmpChannelMaxCapacity; + const maxSize = conf.hrmpChannelMaxMessageSize; + + const senderDeposit = BigInt(conf.hrmpSenderDeposit); + + const requiredBalance = relayFee + senderDeposit; + const sovereignAccount = paraChildSovereignAccount(relayApi, senderParaId); + const balance = await relayApi.query.system.account(sovereignAccount) + .then(accountInfo => (accountInfo.toJSON() as any).data.free as bigint); + + if(!skipBalanceCheck && balance < requiredBalance) { + throw Error(`Not enough balance on the sender's sovereign account: balance(${balance}) < requiredBalance(${requiredBalance})`); + } + + return relayApi.tx.hrmp.hrmpInitOpenChannel( + receiverParaId, + maxCapacity, + maxSize, + ).method.toHex(); +}; + +const proposeAcceptChannel = async (relayApi: ApiPromise, relayFee: bigint, senderParaId: number, recipientParaId: number, skipBalanceCheck: boolean) => { + const conf: any = await relayApi.query.configuration.activeConfig() + .then(data => data.toJSON()); + const recipientDeposit = BigInt(conf.hrmpRecipientDeposit); + + const requiredBalance = relayFee + recipientDeposit; + + const sovereignAccount = paraChildSovereignAccount(relayApi, recipientParaId); + const balance = await relayApi.query.system.account(sovereignAccount) + .then(accountInfo => (accountInfo.toJSON() as any).data.free as bigint); + + if(!skipBalanceCheck && balance < requiredBalance) { + throw Error(`Not enough balance on the recipient's sovereign account: balance(${balance}) < requiredBalance(${requiredBalance})`); + } + + return relayApi.tx.hrmp.hrmpAcceptOpenChannel(senderParaId).method.toHex(); +}; + +async function main() { + let skipBalanceCheck; + if(process.argv.length == 6) { + skipBalanceCheck = false; + } + else if(process.argv.length == 7 && process.argv[6] == 'skip-balance-check') { + skipBalanceCheck = true; + } else { + console.log('Usage: yarn hrmpChannel [skip-balance-check]'); + process.exit(1); + } + + const relayUrl = process.argv[2]; + const uniqueParachainUrl = process.argv[3]; + const otherParachainUrl = process.argv[4]; + const op = process.argv[5]; + + const relayWsProvider = new WsProvider(relayUrl); + const relayApi = await ApiPromise.create({provider: relayWsProvider}); + + const uniqueWsProvider = new WsProvider(uniqueParachainUrl); + const uniqueApi = await ApiPromise.create({provider: uniqueWsProvider}); + + const otherWsProvider = new WsProvider(otherParachainUrl); + const otherApi = await ApiPromise.create({provider: otherWsProvider}); + + const uniqueParaId = await uniqueApi.query.parachainInfo.parachainId() + .then(data => data.toJSON() as number); + + const otherParaId = await otherApi.query.parachainInfo.parachainId() + .then(data => data.toJSON() as number); + + const relayDecimals = await relayApi.rpc.system.properties() + .then(data => data.tokenDecimals.unwrap()[0].toNumber()); + + const relayFee = 2n * BigInt(10 ** relayDecimals); + + let encodedRelayCall: string; + + if(op == 'open') { + encodedRelayCall = await proposeOpenChannel(relayApi, relayFee, uniqueParaId, otherParaId, skipBalanceCheck); + } else if(op == 'accept') { + encodedRelayCall = await proposeAcceptChannel(relayApi, relayFee, otherParaId, uniqueParaId, skipBalanceCheck); + } else { + throw Error(`Unknown hrmp channel operation: ${op}`); + } + + const proposal = uniqueApi.tx.polkadotXcm.send( + { + V3: { + parents: 1, + interior: 'Here', + }, + }, + { + V3: [ + { + WithdrawAsset: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: relayFee, + }, + }, + ], + }, + { + BuyExecution: { + fees: { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: relayFee, + }, + }, + weightLimit: 'Unlimited', + }, + }, + { + Transact: { + originKind: 'Native', + requireWeightAtMost: { + refTime: 1000000000, + proofSize: 65536, + }, + call: { + encoded: encodedRelayCall, + }, + }, + }, + 'RefundSurplus', + { + DepositAsset: { + assets: { + Wild: { + AllCounted: 1, + }, + }, + beneficiary: { + parents: 0, + interior: { + X1: { + Parachain: uniqueParaId, + }, + }, + }, + }, + }, + ], + }, + ).method.toHex(); + + const councilMembers = (await uniqueApi.query.council.members()).toJSON() as any[]; + const councilProposalThreshold = Math.floor(councilMembers.length / 2) + 1; + + const democracyProposalHash = blake2AsHex(proposal, 256); + const democracyProposalPreimage = uniqueApi.tx.preimage.notePreimage(proposal).method.toHex(); + + const democracyProposal = uniqueApi.tx.democracy.externalProposeDefault({ + Legacy: democracyProposalHash, + }); + + const councilProposal = uniqueApi.tx.council.propose( + councilProposalThreshold, + democracyProposal, + democracyProposal.method.encodedLength, + ).method.toHex(); + + const proposeBatch = uniqueApi.tx.utility.batchAll([ + democracyProposalPreimage, + councilProposal, + ]); + + const encodedCall = proposeBatch.method.toHex(); + + console.log('-----------------'); + console.log('Council Proposal: ', `https://polkadot.js.org/apps/?rpc=${uniqueParachainUrl}#/extrinsics/decode/${encodedCall}`); + console.log('-----------------'); + + await relayApi.disconnect(); + await uniqueApi.disconnect(); + await otherApi.disconnect(); +} + +await main(); --- a/js-packages/scripts/package.json +++ b/js-packages/scripts/package.json @@ -13,8 +13,9 @@ "benchMintingFee": "yarn ts-node benchmarks/mintFee/benchmark.ts", "loadTransfer": "yarn ts-node transfer.nload.ts", "generateEnv": "yarn ts-node --esm ./generateEnv.ts", - "propose-upgrade": "yarn ts-node --esm ./proposeupgrade.ts", - "propose-fast-track": "yarn ts-node --esm ./proposefasttrack.ts", - "council-democracy-proposal": "yarn ts-node --esm ./councilproposal.ts" + "proposeUpgrade": "yarn ts-node --esm ./proposeupgrade.ts", + "proposeFastTrack": "yarn ts-node --esm ./proposefasttrack.ts", + "councilDemocracyProposal": "yarn ts-node --esm ./councilproposal.ts", + "hrmpChannel": "yarn ts-node --esm ./hrmpchannel.ts" } } --- a/js-packages/scripts/proposeupgrade.ts +++ b/js-packages/scripts/proposeupgrade.ts @@ -3,6 +3,11 @@ import {readFileSync} from 'fs'; async function main() { + if(process.argv.length != 4) { + console.log('Usage: proposeUpgrade '); + process.exit(1); + } + const networkUrl = process.argv[2]; const wasmFile = process.argv[3]; -- gitstuff