From 17a52a581b90e05a1bd81ef4e5050887fe9b3ca7 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Thu, 06 Oct 2022 15:01:08 +0000 Subject: [PATCH] feat: add nested ops - scheduled and sudo --- --- a/tests/src/eth/util/playgrounds/index.ts +++ b/tests/src/eth/util/playgrounds/index.ts @@ -39,7 +39,6 @@ } finally { await helper.disconnect(); - await helper.disconnectWeb3(); silentConsole.disable(); } }; --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -321,6 +321,7 @@ } } +export type EthUniqueHelperConstructor = new (...args: any[]) => EthUniqueHelper; export class EthUniqueHelper extends DevUniqueHelper { web3: Web3 | null = null; @@ -331,8 +332,10 @@ ethNativeContract: NativeContractGroup; ethContract: ContractGroup; - constructor(logger: { log: (msg: any, level: any) => void, level: any }) { - super(logger); + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? EthUniqueHelper; + + super(logger, options); this.eth = new EthGroup(this); this.ethAddress = new EthAddressGroup(this); this.ethNativeContract = new NativeContractGroup(this); @@ -350,10 +353,23 @@ this.web3 = new Web3(this.web3Provider); } - async disconnectWeb3() { + async disconnect() { if(this.web3 === null) return; this.web3Provider?.connection.close(); + + await super.disconnect(); + } + + clearApi() { this.web3 = null; } + + clone(helperCls: EthUniqueHelperConstructor, options?: { [key: string]: any; }): EthUniqueHelper { + const newHelper = super.clone(helperCls, options) as EthUniqueHelper; + newHelper.web3 = this.web3; + newHelper.web3Provider = this.web3Provider; + + return newHelper; + } } \ No newline at end of file --- a/tests/src/util/playgrounds/types.ts +++ b/tests/src/util/playgrounds/types.ts @@ -164,6 +164,14 @@ amount: bigint, } +export interface ISchedulerOptions { + priority?: number, + periodic?: { + period: number, + repetitions: number, + }, +} + export type TSubstrateAccount = string; export type TEthereumAccount = string; export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated'; --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -7,6 +7,9 @@ import * as defs from '../../interfaces/definitions'; import {IKeyringPair} from '@polkadot/types/types'; import {ICrossAccountId} from './types'; +import type {EventRecord} from '@polkadot/types/interfaces'; +import {VoidFn} from '@polkadot/api/types'; +import {FrameSystemEventRecord} from '@polkadot/types/lookup'; export class SilentLogger { @@ -63,8 +66,10 @@ wait: WaitGroup; admin: AdminGroup; - constructor(logger: { log: (msg: any, level: any) => void, level: any }) { - super(logger); + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevUniqueHelper; + + super(logger, options); this.arrange = new ArrangeGroup(this); this.wait = new WaitGroup(this); this.admin = new AdminGroup(this); @@ -108,9 +113,9 @@ } class ArrangeGroup { - helper: UniqueHelper; + helper: DevUniqueHelper; - constructor(helper: UniqueHelper) { + constructor(helper: DevUniqueHelper) { this.helper = helper; } @@ -245,14 +250,14 @@ } class WaitGroup { - helper: UniqueHelper; + helper: DevUniqueHelper; - constructor(helper: UniqueHelper) { + constructor(helper: DevUniqueHelper) { this.helper = helper; } /** - * Wait for specified bnumber of blocks + * Wait for specified number of blocks * @param blocksCount number of blocks to wait * @returns */ --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -9,7 +9,8 @@ import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types'; import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto'; import {IKeyringPair} from '@polkadot/types/types'; -import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types'; +import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types'; +import {RuntimeDispatchInfo} from '@polkadot/types/interfaces'; export class CrossAccountId implements ICrossAccountId { Substrate?: TSubstrateAccount; @@ -318,6 +319,7 @@ forcedNetwork: TUniqueNetworks | null; network: TUniqueNetworks | null; chainLog: IUniqueHelperLog[]; + children: ChainHelperBase[]; constructor(logger?: ILogger) { this.util = UniqueUtil; @@ -328,6 +330,7 @@ this.forcedNetwork = null; this.network = null; this.chainLog = []; + this.children = []; } getApi(): ApiPromise { @@ -351,8 +354,16 @@ } async disconnect() { + for (const child of this.children) { + child.clearApi(); + } + if (this.api === null) return; await this.api.disconnect(); + this.clearApi(); + } + + clearApi() { this.api = null; this.network = null; } @@ -479,7 +490,7 @@ constructApiCall(apiCall: string, params: any[]) { if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`); - let call = this.api as any; + let call = this.getApi() as any; for(const part of apiCall.slice(4).split('.')) { call = call[part]; } @@ -2248,7 +2259,67 @@ } } +class SchedulerGroup extends HelperGroup { + constructor(helper: UniqueHelper) { + super(helper); + } + + async cancelScheduled(signer: TSigner, scheduledId: string) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.scheduler.cancelNamed', + [scheduledId], + true, + ); + } + + async changePriority(signer: TSigner, scheduledId: string, priority: number) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.scheduler.changeNamedPriority', + [scheduledId, priority], + true, + ); + } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + return this.schedule('scheduleNamed', scheduledId, executionBlockNumber, options); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + return this.schedule('scheduleNamedAfter', scheduledId, blocksBeforeExecution, options); + } + + schedule( + scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter', + scheduledId: string, + blocksNum: number, + options: ISchedulerOptions = {}, + ) { + // eslint-disable-next-line @typescript-eslint/naming-convention + const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase); + return this.helper.clone(ScheduledHelperType, { + scheduleFn, + scheduledId, + blocksNum, + options, + }) as T; + } +} + +export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper; + export class UniqueHelper extends ChainHelperBase { + helperBase: any; + chain: ChainGroup; balance: BalanceGroup; address: AddressGroup; @@ -2257,9 +2328,13 @@ rft: RFTGroup; ft: FTGroup; staking: StakingGroup; + scheduler: SchedulerGroup; - constructor(logger?: ILogger) { + constructor(logger?: ILogger, options: {[key: string]: any} = {}) { super(logger); + + this.helperBase = options.helperBase ?? UniqueHelper; + this.chain = new ChainGroup(this); this.balance = new BalanceGroup(this); this.address = new AddressGroup(this); @@ -2268,9 +2343,98 @@ this.rft = new RFTGroup(this); this.ft = new FTGroup(this); this.staking = new StakingGroup(this); + this.scheduler = new SchedulerGroup(this); } + + clone(helperCls: UniqueHelperConstructor, options: {[key: string]: any} = {}) { + Object.setPrototypeOf(helperCls.prototype, this); + const newHelper = new helperCls(this.logger, options); + + newHelper.api = this.api; + newHelper.network = this.network; + newHelper.forceNetwork = this.forceNetwork; + + this.children.push(newHelper); + + return newHelper; + } + + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoUniqueHelper(this.helperBase); + return this.clone(SudoHelperType) as T; + } } +// eslint-disable-next-line @typescript-eslint/naming-convention +function ScheduledUniqueHelper(Base: T) { + return class extends Base { + scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter'; + scheduledId: string; + blocksNum: number; + options: ISchedulerOptions; + + constructor(...args: any[]) { + const logger = args[0] as ILogger; + const options = args[1] as { + scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter', + scheduledId: string, + blocksNum: number, + options: ISchedulerOptions + }; + + super(logger); + + this.scheduleFn = options.scheduleFn; + this.scheduledId = options.scheduledId; + this.blocksNum = options.blocksNum; + this.options = options.options; + } + + executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise { + const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams); + const extrinsic = 'api.tx.scheduler.' + this.scheduleFn; + + return super.executeExtrinsic( + sender, + extrinsic, + [ + this.scheduledId, + this.blocksNum, + this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null, + this.options.priority ?? null, + {Value: scheduledTx}, + ], + expectSuccess, + ); + } + }; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +function SudoUniqueHelper(Base: T) { + return class extends Base { + constructor(...args: any[]) { + super(...args); + } + + executeExtrinsic ( + sender: IKeyringPair, + extrinsic: string, + params: any[], + expectSuccess?: boolean, + ): Promise { + const call = this.constructApiCall(extrinsic, params); + + return super.executeExtrinsic( + sender, + 'api.tx.sudo.sudo', + [call], + expectSuccess, + ); + } + }; +} export class UniqueBaseCollection { helper: UniqueHelper; @@ -2372,6 +2536,28 @@ async burn(signer: TSigner) { return await this.helper.collection.burn(signer, this.collectionId); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueBaseCollection(this.collectionId, scheduledHelper); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueBaseCollection(this.collectionId, scheduledHelper); + } + + getSudo() { + return new UniqueBaseCollection(this.collectionId, this.helper.getSudo()); + } } @@ -2459,6 +2645,28 @@ async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) { return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueNFTCollection(this.collectionId, scheduledHelper); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueNFTCollection(this.collectionId, scheduledHelper); + } + + getSudo() { + return new UniqueNFTCollection(this.collectionId, this.helper.getSudo()); + } } @@ -2542,6 +2750,28 @@ async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) { return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueRFTCollection(this.collectionId, scheduledHelper); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueRFTCollection(this.collectionId, scheduledHelper); + } + + getSudo() { + return new UniqueRFTCollection(this.collectionId, this.helper.getSudo()); + } } @@ -2589,6 +2819,28 @@ async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) { return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueFTCollection(this.collectionId, scheduledHelper); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueFTCollection(this.collectionId, scheduledHelper); + } + + getSudo() { + return new UniqueFTCollection(this.collectionId, this.helper.getSudo()); + } } @@ -2626,6 +2878,28 @@ nestingAccount() { return this.collection.helper.util.getTokenAccount(this); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueBaseToken(this.tokenId, scheduledCollection); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueBaseToken(this.tokenId, scheduledCollection); + } + + getSudo() { + return new UniqueBaseToken(this.tokenId, this.collection.getSudo()); + } } @@ -2684,6 +2958,28 @@ async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) { return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueNFToken(this.tokenId, scheduledCollection); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueNFToken(this.tokenId, scheduledCollection); + } + + getSudo() { + return new UniqueNFToken(this.tokenId, this.collection.getSudo()); + } } export class UniqueRFToken extends UniqueBaseToken { @@ -2737,4 +3033,26 @@ async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) { return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueRFToken(this.tokenId, scheduledCollection); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueRFToken(this.tokenId, scheduledCollection); + } + + getSudo() { + return new UniqueRFToken(this.tokenId, this.collection.getSudo()); + } } -- gitstuff