git.delta.rocks / unique-network / refs/commits / 2f6a65e5b93a

difftreelog

Merge pull request #601 from UniqueNetwork/tests/event-logging-and-more

ut-akuznetsov2022-09-20parents: #488175c #155f75c.patch.diff
in: master

8 files changed

modifiedtests/src/app-promotion.test.tsdiffbeforeafterboth
555 const flipper = await helper.eth.deployFlipper(contractOwner);555 const flipper = await helper.eth.deployFlipper(contractOwner);
556 556
557 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);557 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);
558 const stopSponsoringResult = await helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address]);558 await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address]))
559 expect(stopSponsoringResult.status).to.equal('Fail');559 .to.be.rejectedWith(/appPromotion\.NoPermission/);
560 expect(stopSponsoringResult.moduleError).to.equal('appPromotion.NoPermission');
561 });560 });
562 561
563 itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {562 itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {
modifiedtests/src/fungible.test.tsdiffbeforeafterboth
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
18import {U128_MAX} from './util/helpers';
19import {itSub, usingPlaygrounds, expect} from './util/playgrounds';18import {itSub, usingPlaygrounds, expect} from './util/playgrounds';
2019
21// todo:playgrounds get rid of globals
22let alice: IKeyringPair;20const U128_MAX = (1n << 128n) - 1n;
23let bob: IKeyringPair;
2421
25describe('integration test: Fungible functionality:', () => {22describe('integration test: Fungible functionality:', () => {
23 let alice: IKeyringPair;
24 let bob: IKeyringPair;
25
26 before(async () => {26 before(async () => {
27 await usingPlaygrounds(async (helper, privateKey) => {27 await usingPlaygrounds(async (helper, privateKey) => {
28 alice = privateKey('//Alice');28 const donor = privateKey('//Alice');
29 bob = privateKey('//Bob');29 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
30 });30 });
31 });31 });
3232
82 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(60n);82 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(60n);
83 expect(await collection.getBalance(ethAcc)).to.be.equal(140n);83 expect(await collection.getBalance(ethAcc)).to.be.equal(140n);
8484
85 await expect(collection.transfer(alice, {Substrate: bob.address}, 350n)).to.eventually.be.rejected;85 await expect(collection.transfer(alice, {Substrate: bob.address}, 350n)).to.eventually.be.rejectedWith(/common\.TokenValueTooLow/);
86 });86 });
8787
88 itSub('Tokens multiple creation', async ({helper}) => {88 itSub('Tokens multiple creation', async ({helper}) => {
modifiedtests/src/inflation.test.tsdiffbeforeafterboth
14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import chai from 'chai';
18import chaiAsPromised from 'chai-as-promised';17import {IKeyringPair} from '@polkadot/types/types';
19import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api';18import {expect, itSub, usingPlaygrounds} from './util/playgrounds';
2019
21chai.use(chaiAsPromised);20// todo:playgrounds requires sudo, look into on the later stage
22const expect = chai.expect;
23
24describe('integration test: Inflation', () => {21describe('integration test: Inflation', () => {
22 let superuser: IKeyringPair;
23
24 before(async () => {
25 await usingPlaygrounds(async (_, privateKey) => {
26 superuser = privateKey('//Alice');
27 });
28 });
29
25 it('First year inflation is 10%', async () => {30 itSub('First year inflation is 10%', async ({helper}) => {
26 await usingApi(async (api, privateKeyWrapper) => {
27
28 // Make sure non-sudo can't start inflation31 // Make sure non-sudo can't start inflation
29 const tx = api.tx.inflation.startInflation(1);32 const [bob] = await helper.arrange.createAccounts([10n], superuser);
33
30 const bob = privateKeyWrapper('//Bob');34 await expect(helper.executeExtrinsic(bob, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/);
35
36 // Make sure superuser can't start inflation without explicit sudo
31 await expect(submitTransactionExpectFailAsync(bob, tx)).to.be.rejected;37 await expect(helper.executeExtrinsic(superuser, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/);
3238
33 // Start inflation on relay block 1 (Alice is sudo)39 // Start inflation on relay block 1 (Alice is sudo)
34 const alice = privateKeyWrapper('//Alice');40 const tx = helper.constructApiCall('api.tx.inflation.startInflation', [1]);
35 const sudoTx = api.tx.sudo.sudo(tx as any);41 await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [tx])).to.not.be.rejected;
36 await submitTransactionAsync(alice, sudoTx);
3742
38 const blockInterval = (api.consts.inflation.inflationBlockInterval).toBigInt();43 const blockInterval = (helper.api!.consts.inflation.inflationBlockInterval as any).toBigInt();
39 const totalIssuanceStart = (await api.query.inflation.startingYearTotalIssuance()).toBigInt();44 const totalIssuanceStart = ((await helper.api!.query.inflation.startingYearTotalIssuance()) as any).toBigInt();
40 const blockInflation = (await api.query.inflation.blockInflation()).toBigInt();45 const blockInflation = (await helper.api!.query.inflation.blockInflation() as any).toBigInt();
4146
42 const YEAR = 5259600n; // 6-second block. Blocks in one year47 const YEAR = 5259600n; // 6-second block. Blocks in one year
43 // const YEAR = 2629800n; // 12-second block. Blocks in one year48 // const YEAR = 2629800n; // 12-second block. Blocks in one year
49 const expectedInflation = totalExpectedInflation / totalActualInflation - 1n;54 const expectedInflation = totalExpectedInflation / totalActualInflation - 1n;
5055
51 expect(Math.abs(Number(expectedInflation))).to.be.lessThanOrEqual(tolerance);56 expect(Math.abs(Number(expectedInflation))).to.be.lessThanOrEqual(tolerance);
52 });
53 });57 });
54
55});58});
modifiedtests/src/refungible.test.tsdiffbeforeafterboth
17import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
18import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from './util/playgrounds';18import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from './util/playgrounds';
1919
20let alice: IKeyringPair;
21let bob: IKeyringPair;
22const MAX_REFUNGIBLE_PIECES = 1_000_000_000_000_000_000_000n;20const MAX_REFUNGIBLE_PIECES = 1_000_000_000_000_000_000_000n;
2321
24describe('integration test: Refungible functionality:', async () => {22describe('integration test: Refungible functionality:', async () => {
23 let alice: IKeyringPair;
24 let bob: IKeyringPair;
25
25 before(async function() {26 before(async function() {
26 await usingPlaygrounds(async (helper, privateKey) => {27 await usingPlaygrounds(async (helper, privateKey) => {
27 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);28 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
2829
29 alice = privateKey('//Alice');30 const donor = privateKey('//Alice');
30 bob = privateKey('//Bob');31 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
31 });32 });
32 });33 });
33 34
209 const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});210 const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
210 const token = await collection.mintToken(alice, 100n);211 const token = await collection.mintToken(alice, 100n);
211 await token.repartition(alice, 200n);212 await token.repartition(alice, 200n);
212 const chainEvents = helper.chainLog.slice(-1)[0].events.map((x: any) => x.event);213 const chainEvents = helper.chainLog.slice(-1)[0].events;
213 expect(chainEvents).to.include.deep.members([{214 expect(chainEvents).to.deep.include({
214 method: 'ItemCreated',215 section: 'common',
215 section: 'common',216 method: 'ItemCreated',
216 index: '0x4202',217 index: [66, 2],
217 data: [ 218 data: [
218 helper.api!.createType('u32', collection.collectionId).toHuman(), 219 collection.collectionId,
219 helper.api!.createType('u32', token.tokenId).toHuman(),220 token.tokenId,
220 {Substrate: alice.address}, 221 {substrate: alice.address},
221 '100',222 100n,
222 ],223 ],
224 phase: {applyExtrinsic: 2},
223 }]);225 });
224 });226 });
225227
226 itSub('Repartition with decreased amount', async ({helper}) => {228 itSub('Repartition with decreased amount', async ({helper}) => {
227 const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});229 const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
228 const token = await collection.mintToken(alice, 100n);230 const token = await collection.mintToken(alice, 100n);
229 await token.repartition(alice, 50n);231 await token.repartition(alice, 50n);
230 const chainEvents = helper.chainLog.slice(-1)[0].events.map((x: any) => x.event);232 const chainEvents = helper.chainLog.slice(-1)[0].events;
231 expect(chainEvents).to.include.deep.members([{233 expect(chainEvents).to.deep.include({
232 method: 'ItemDestroyed',234 method: 'ItemDestroyed',
233 section: 'common',235 section: 'common',
234 index: '0x4203',236 index: [66, 3],
235 data: [ 237 data: [
236 helper.api!.createType('u32', collection.collectionId).toHuman(), 238 collection.collectionId,
237 helper.api!.createType('u32', token.tokenId).toHuman(),239 token.tokenId,
238 {Substrate: alice.address}, 240 {substrate: alice.address},
239 '50',241 50n,
240 ],242 ],
243 phase: {applyExtrinsic: 2},
241 }]);244 });
242 });245 });
243 246
244 itSub('Create new collection with properties', async ({helper}) => {247 itSub('Create new collection with properties', async ({helper}) => {
modifiedtests/src/tx-version-presence.test.tsdiffbeforeafterboth

no syntactic changes

modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
33
4import {IKeyringPair} from '@polkadot/types/types';4import {IKeyringPair} from '@polkadot/types/types';
55
6export interface IChainEvent {6export interface IEvent {
7 data: any;7 section: string;
8 method: string;8 method: string;
9 section: string;9 index: [number, number] | string;
10 data: any[];
11 phase: {applyExtrinsic: number} | 'Initialization',
10}12}
1113
12export interface ITransactionResult {14export interface ITransactionResult {
13 status: 'Fail' | 'Success';15 status: 'Fail' | 'Success';
14 result: {16 result: {
15 events: {17 events: {
18 phase: any, // {ApplyExtrinsic: number} | 'Initialization',
16 event: IChainEvent19 event: IEvent;
17 }[];20 }[];
18 },21 },
19 moduleError?: string;22 moduleError?: string;
20}23}
24
25export interface ISubscribeBlockEventsData {
26 number: number;
27 hash: string;
28 timestamp: number;
29 events: IEvent[];
30}
2131
22export interface ILogger {32export interface ILogger {
23 log: (msg: any, level?: string) => void;33 log: (msg: any, level?: string) => void;
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
132 accounts.push(recipient);132 accounts.push(recipient);
133 if (balance !== 0n) {133 if (balance !== 0n) {
134 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);134 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);
135 transactions.push(this.helper.signTransaction(donor, tx, 'account generation', {nonce}));135 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));
136 nonce++;136 nonce++;
137 }137 }
138 }138 }
183 accounts.push(recepient);183 accounts.push(recepient);
184 if (withBalance !== 0n) {184 if (withBalance !== 0n) {
185 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, withBalance * tokenNominal]);185 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, withBalance * tokenNominal]);
186 transactions.push(this.helper.signTransaction(donor, tx, 'account generation', {nonce}));186 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));
187 nonce++;187 nonce++;
188 }188 }
189 }189 }
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
6/* eslint-disable no-prototype-builtins */6/* eslint-disable no-prototype-builtins */
77
8import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';8import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';
9import {ApiInterfaceEvents} from '@polkadot/api/types';9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';
10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';
11import {IKeyringPair} from '@polkadot/types/types';11import {IKeyringPair} from '@polkadot/types/types';
12import {IApiListeners, IBlock, IChainEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types';12import {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';
1313
14export const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {14export const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {
15 const address = {} as ICrossAccountId;15 const address = {} as ICrossAccountId;
149 return {success, tokens};149 return {success, tokens};
150 }150 }
151151
152 static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string) {152 static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string) {
153 let eventId = null;153 let eventId = null;
154 events.forEach(({event: {data, method, section}}) => {154 events.forEach(({event: {data, method, section}}) => {
155 if ((section === expectedSection) && (method === expectedMethod)) {155 if ((section === expectedSection) && (method === expectedMethod)) {
163 return eventId === collectionId;163 return eventId === collectionId;
164 }164 }
165165
166 static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {166 static isTokenTransferSuccess(events: {event: IEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {
167 const normalizeAddress = (address: string | ICrossAccountId) => {167 const normalizeAddress = (address: string | ICrossAccountId) => {
168 if(typeof address === 'string') return address;168 if(typeof address === 'string') return address;
169 const obj = {} as any;169 const obj = {} as any;
195 }195 }
196}196}
197197
198class UniqueEventHelper {
199 private static extractIndex(index: any): [number, number] | string {
200 if(index.toRawType() === '[u8;2]') return [index[0], index[1]];
201 return index.toJSON();
202 }
203
204 private static extractSub(data: any, subTypes: any): {[key: string]: any} {
205 let obj: any = {};
206 let index = 0;
207
208 if (data.entries) {
209 for(const [key, value] of data.entries()) {
210 obj[key] = this.extractData(value, subTypes[index]);
211 index++;
212 }
213 } else obj = data.toJSON();
214
215 return obj;
216 }
217
218 private static extractData(data: any, type: any): any {
219 if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();
220 if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();
221 if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);
222 return data.toHuman();
223 }
224
225 public static extractEvents(records: ITransactionResult): IEvent[] {
226 const parsedEvents: IEvent[] = [];
227
228 records.result.events.forEach((record) => {
229 const {event, phase} = record;
230 const types = (event as any).typeDef;
231
232 const eventData: IEvent = {
233 section: event.section.toString(),
234 method: event.method.toString(),
235 index: this.extractIndex(event.index),
236 data: [],
237 phase: phase.toJSON(),
238 };
239
240 event.data.forEach((val: any, index: number) => {
241 eventData.data.push(this.extractData(val, types[index]));
242 });
243
244 parsedEvents.push(eventData);
245 });
246
247 return parsedEvents;
248 }
249}
198250
199class ChainHelperBase {251class ChainHelperBase {
200 transactionStatus = UniqueUtil.transactionStatus;252 transactionStatus = UniqueUtil.transactionStatus;
201 chainLogType = UniqueUtil.chainLogType;253 chainLogType = UniqueUtil.chainLogType;
202 util: typeof UniqueUtil;254 util: typeof UniqueUtil;
255 eventHelper: typeof UniqueEventHelper;
203 logger: ILogger;256 logger: ILogger;
204 api: ApiPromise | null;257 api: ApiPromise | null;
205 forcedNetwork: TUniqueNetworks | null;258 forcedNetwork: TUniqueNetworks | null;
208261
209 constructor(logger?: ILogger) {262 constructor(logger?: ILogger) {
210 this.util = UniqueUtil;263 this.util = UniqueUtil;
264 this.eventHelper = UniqueEventHelper;
211 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();265 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();
212 this.logger = logger;266 this.logger = logger;
213 this.api = null;267 this.api = null;
290 return {api, network};344 return {api, network};
291 }345 }
292346
293 getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) {347 getTransactionStatus(data: {events: {event: IEvent}[], status: any}) {
294 const {events, status} = data;348 const {events, status} = data;
295 if (status.isReady) {349 if (status.isReady) {
296 return this.transactionStatus.NOT_READY;350 return this.transactionStatus.NOT_READY;
299 return this.transactionStatus.NOT_READY;353 return this.transactionStatus.NOT_READY;
300 }354 }
301 if (status.isInBlock || status.isFinalized) {355 if (status.isInBlock || status.isFinalized) {
302 const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed');356 const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');
303 if (errors.length > 0) {357 if (errors.length > 0) {
304 return this.transactionStatus.FAIL;358 return this.transactionStatus.FAIL;
305 }359 }
306 if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {360 if (events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {
307 return this.transactionStatus.SUCCESS;361 return this.transactionStatus.SUCCESS;
308 }362 }
309 }363 }
310364
311 return this.transactionStatus.FAIL;365 return this.transactionStatus.FAIL;
312 }366 }
313367
314 signTransaction(sender: TSigner, transaction: any, label = 'transaction', options: any = null) {368 signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {
315 const sign = (callback: any) => {369 const sign = (callback: any) => {
316 if(options !== null) return transaction.signAndSend(sender, options, callback);370 if(options !== null) return transaction.signAndSend(sender, options, callback);
317 return transaction.signAndSend(sender, callback);371 return transaction.signAndSend(sender, callback);
332 if (result.hasOwnProperty('dispatchError')) {386 if (result.hasOwnProperty('dispatchError')) {
333 const dispatchError = result['dispatchError'];387 const dispatchError = result['dispatchError'];
334388
335 if (dispatchError && dispatchError.isModule) {389 if (dispatchError) {
390 if (dispatchError.isModule) {
336 const modErr = dispatchError.asModule;391 const modErr = dispatchError.asModule;
337 const errorMeta = dispatchError.registry.findMetaError(modErr);392 const errorMeta = dispatchError.registry.findMetaError(modErr);
338393
339 moduleError = `${errorMeta.section}.${errorMeta.name}`;394 moduleError = `${errorMeta.section}.${errorMeta.name}`;
340 }395 } else {
396 moduleError = dispatchError.toHuman();
397 }
341 else {398 } else {
342 this.logger.log(result, this.logger.level.ERROR);399 this.logger.log(result, this.logger.level.ERROR);
343 }400 }
344 }401 }
364 return call(...params);421 return call(...params);
365 }422 }
366423
367 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false/*, failureMessage='expected success'*/) {424 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {
368 if(this.api === null) throw Error('API not initialized');425 if(this.api === null) throw Error('API not initialized');
369 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);426 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);
370427
371 const startTime = (new Date()).getTime();428 const startTime = (new Date()).getTime();
372 let result: ITransactionResult;429 let result: ITransactionResult;
373 let events = [];430 let events: IEvent[] = [];
374 try {431 try {
375 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult;432 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;
376 events = result.result.events.map((x: any) => x.toHuman());433 events = this.eventHelper.extractEvents(result);
377 }434 }
378 catch(e) {435 catch(e) {
379 if(!(e as object).hasOwnProperty('status')) throw e;436 if(!(e as object).hasOwnProperty('status')) throw e;