diff --git a/src/Claims.ts b/src/Claims.ts index f78e81c406..eb483b2f8d 100644 --- a/src/Claims.ts +++ b/src/Claims.ts @@ -84,10 +84,10 @@ export class Claims { includeExpired?: boolean; size?: number; start?: number; - } = { includeExpired: true } + } = {} ): Promise> { const { context } = this; - const { target, includeExpired, size, start } = opts; + const { target, includeExpired = true, size, start } = opts; const did = await getDid(target, context); @@ -124,11 +124,19 @@ export class Claims { includeExpired?: boolean; size?: number; start?: number; - } = { includeExpired: true } + } = {} ): Promise> { const { context } = this; - const { targets, trustedClaimIssuers, scope, claimTypes, includeExpired, size, start } = opts; + const { + targets, + trustedClaimIssuers, + scope, + claimTypes, + includeExpired = true, + size, + start, + } = opts; const result = await context.queryMiddleware>( didsWithClaims({ @@ -214,10 +222,10 @@ export class Claims { includeExpired?: boolean; size?: number; start?: number; - } = { includeExpired: true } + } = {} ): Promise> { const { context } = this; - const { target, includeExpired, size, start } = opts; + const { target, includeExpired = true, size, start } = opts; const did = await getDid(target, context); @@ -249,11 +257,11 @@ export class Claims { includeExpired?: boolean; size?: number; start?: number; - } = { includeExpired: true } + } = {} ): Promise> { const { context } = this; - const { target, trustedClaimIssuers, scope, includeExpired, size, start } = opts; + const { target, trustedClaimIssuers, scope, includeExpired = true, size, start } = opts; const did = await getDid(target, context); diff --git a/src/api/entities/CurrentIdentity.ts b/src/api/entities/CurrentIdentity.ts index 1602517a41..8c7bbc1541 100644 --- a/src/api/entities/CurrentIdentity.ts +++ b/src/api/entities/CurrentIdentity.ts @@ -1,4 +1,6 @@ import { u64 } from '@polkadot/types'; +import P from 'bluebird'; +import { chunk, flatten, uniqBy } from 'lodash'; import { Instruction as MeshInstruction } from 'polymesh-types/types'; import { Identity, Instruction, Venue } from '~/api/entities'; @@ -11,7 +13,9 @@ import { } from '~/api/procedures'; import { TransactionQueue } from '~/base'; import { SecondaryKey, Signer, SubCallback, UnsubCallback } from '~/types'; +import { PortfolioId } from '~/types/internal'; import { portfolioIdToMeshPortfolioId, u64ToBigNumber } from '~/utils'; +import { MAX_CONCURRENT_REQUESTS } from '~/utils/constants'; /** * Represents the Identity associated to the current [[Account]] @@ -74,20 +78,34 @@ export class CurrentIdentity extends Identity { }, }, did, + portfolios, context, } = this; - const auths = await settlement.userAuths.entries( - portfolioIdToMeshPortfolioId({ did }, context) - ); + const [, ...numberedPortfolios] = await portfolios.getPortfolios(); - const instructionIds = auths.map(([key]) => key.args[1] as u64); + const portfolioIds: PortfolioId[] = [ + { did }, + ...numberedPortfolios.map(({ id: number }) => ({ did, number })), + ]; - const meshInstructions = await settlement.instructionDetails.multi( - instructionIds - ); + const portfolioIdChunks = chunk(portfolioIds, MAX_CONCURRENT_REQUESTS); - return meshInstructions + const chunkedInstructions = await P.mapSeries(portfolioIdChunks, async portfolioIdChunk => { + const auths = await P.map(portfolioIdChunk, portfolioId => + settlement.userAuths.entries(portfolioIdToMeshPortfolioId(portfolioId, context)) + ); + + const instructionIds = uniqBy( + flatten(auths).map(([key]) => key.args[1] as u64), + id => id.toNumber() + ); + return settlement.instructionDetails.multi(instructionIds); + }); + + const rawInstructions = flatten(chunkedInstructions); + + return rawInstructions .filter(({ status }) => status.isPending) .map(({ instruction_id: id }) => new Instruction({ id: u64ToBigNumber(id) }, context)); } diff --git a/src/api/entities/__tests__/CurrentIdentity.ts b/src/api/entities/__tests__/CurrentIdentity.ts index 89b7210c84..d72f0b3eeb 100644 --- a/src/api/entities/__tests__/CurrentIdentity.ts +++ b/src/api/entities/__tests__/CurrentIdentity.ts @@ -7,6 +7,7 @@ import { Context, TransactionQueue } from '~/base'; import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks'; import { SecondaryKey, SubCallback, VenueType } from '~/types'; import { tuple } from '~/types/utils'; +import * as utilsModule from '~/utils'; describe('CurrentIdentity class', () => { let context: Context; @@ -141,67 +142,148 @@ describe('CurrentIdentity class', () => { const id1 = new BigNumber(1); const id2 = new BigNumber(2); const id3 = new BigNumber(3); + const id4 = new BigNumber(4); + const id5 = new BigNumber(5); const did = 'someDid'; const identity = new CurrentIdentity({ did }, context); + const numberedPortfolioId = new BigNumber(1); + + identity.portfolios.getPortfolios = sinon + .stub() + .resolves([{ owner: { did } }, { owner: { did }, id: numberedPortfolioId }]); + const rawPortfolio = dsMockUtils.createMockPortfolioId({ did: dsMockUtils.createMockIdentityId(did), kind: dsMockUtils.createMockPortfolioKind('Default'), }); - dsMockUtils.createQueryStub('settlement', 'userAuths', { - entries: [ + + const rawNumberedPortfolio = dsMockUtils.createMockPortfolioId({ + did: dsMockUtils.createMockIdentityId(did), + kind: dsMockUtils.createMockPortfolioKind({ + User: dsMockUtils.createMockU64(numberedPortfolioId.toNumber()), + }), + }); + + const portfolioIdToMeshPortfolioIdStub = sinon.stub( + utilsModule, + 'portfolioIdToMeshPortfolioId' + ); + + portfolioIdToMeshPortfolioIdStub.withArgs({ did }, context).returns(rawPortfolio); + portfolioIdToMeshPortfolioIdStub + .withArgs({ did, number: numberedPortfolioId }, context) + .returns(rawNumberedPortfolio); + + const userAuthsStub = dsMockUtils.createQueryStub('settlement', 'userAuths'); + + const rawId1 = dsMockUtils.createMockU64(id1.toNumber()); + const rawId2 = dsMockUtils.createMockU64(id2.toNumber()); + const rawId3 = dsMockUtils.createMockU64(id3.toNumber()); + const rawId4 = dsMockUtils.createMockU64(id4.toNumber()); + const rawId5 = dsMockUtils.createMockU64(id5.toNumber()); + + const entriesStub = sinon.stub(); + entriesStub + .withArgs(rawPortfolio) + .resolves([ tuple( - [rawPortfolio, dsMockUtils.createMockU64(id1.toNumber())], + { args: [rawPortfolio, rawId1] }, dsMockUtils.createMockAuthorizationStatus('Pending') ), tuple( - [rawPortfolio, dsMockUtils.createMockU64(id2.toNumber())], + { args: [rawPortfolio, rawId2] }, dsMockUtils.createMockAuthorizationStatus('Pending') ), tuple( - [rawPortfolio, dsMockUtils.createMockU64(id3.toNumber())], + { args: [rawPortfolio, rawId3] }, dsMockUtils.createMockAuthorizationStatus('Pending') ), - ], - }); + ]); + + entriesStub + .withArgs(rawNumberedPortfolio) + .resolves([ + tuple( + { args: [rawNumberedPortfolio, rawId2] }, + dsMockUtils.createMockAuthorizationStatus('Pending') + ), + tuple( + { args: [rawNumberedPortfolio, rawId4] }, + dsMockUtils.createMockAuthorizationStatus('Pending') + ), + tuple( + { args: [rawNumberedPortfolio, rawId5] }, + dsMockUtils.createMockAuthorizationStatus('Pending') + ), + ]); + + userAuthsStub.entries = entriesStub; /* eslint-disable @typescript-eslint/camelcase */ - dsMockUtils.createQueryStub('settlement', 'instructionDetails', { - multi: [ - dsMockUtils.createMockInstruction({ - instruction_id: dsMockUtils.createMockU64(id1.toNumber()), - venue_id: dsMockUtils.createMockU64(), - status: dsMockUtils.createMockInstructionStatus('Pending'), - settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'), - created_at: dsMockUtils.createMockOption(), - valid_from: dsMockUtils.createMockOption(), - }), - dsMockUtils.createMockInstruction({ - instruction_id: dsMockUtils.createMockU64(id2.toNumber()), - venue_id: dsMockUtils.createMockU64(), - status: dsMockUtils.createMockInstructionStatus('Pending'), - settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'), - created_at: dsMockUtils.createMockOption(), - valid_from: dsMockUtils.createMockOption(), - }), - dsMockUtils.createMockInstruction({ - instruction_id: dsMockUtils.createMockU64(id3.toNumber()), - venue_id: dsMockUtils.createMockU64(), - status: dsMockUtils.createMockInstructionStatus('Unknown'), - settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'), - created_at: dsMockUtils.createMockOption(), - valid_from: dsMockUtils.createMockOption(), - }), - ], - }); + const instructionDetailsStub = dsMockUtils.createQueryStub( + 'settlement', + 'instructionDetails', + { + multi: [], + } + ); + + const multiStub = sinon.stub(); + + multiStub.withArgs([rawId1, rawId2, rawId3, rawId4, rawId5]).resolves([ + dsMockUtils.createMockInstruction({ + instruction_id: dsMockUtils.createMockU64(id1.toNumber()), + venue_id: dsMockUtils.createMockU64(), + status: dsMockUtils.createMockInstructionStatus('Pending'), + settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'), + created_at: dsMockUtils.createMockOption(), + valid_from: dsMockUtils.createMockOption(), + }), + dsMockUtils.createMockInstruction({ + instruction_id: dsMockUtils.createMockU64(id2.toNumber()), + venue_id: dsMockUtils.createMockU64(), + status: dsMockUtils.createMockInstructionStatus('Pending'), + settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'), + created_at: dsMockUtils.createMockOption(), + valid_from: dsMockUtils.createMockOption(), + }), + dsMockUtils.createMockInstruction({ + instruction_id: dsMockUtils.createMockU64(id3.toNumber()), + venue_id: dsMockUtils.createMockU64(), + status: dsMockUtils.createMockInstructionStatus('Unknown'), + settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'), + created_at: dsMockUtils.createMockOption(), + valid_from: dsMockUtils.createMockOption(), + }), + dsMockUtils.createMockInstruction({ + instruction_id: dsMockUtils.createMockU64(id4.toNumber()), + venue_id: dsMockUtils.createMockU64(), + status: dsMockUtils.createMockInstructionStatus('Pending'), + settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'), + created_at: dsMockUtils.createMockOption(), + valid_from: dsMockUtils.createMockOption(), + }), + dsMockUtils.createMockInstruction({ + instruction_id: dsMockUtils.createMockU64(id5.toNumber()), + venue_id: dsMockUtils.createMockU64(), + status: dsMockUtils.createMockInstructionStatus('Unknown'), + settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'), + created_at: dsMockUtils.createMockOption(), + valid_from: dsMockUtils.createMockOption(), + }), + ]); + + instructionDetailsStub.multi = multiStub; /* eslint-enable @typescript-eslint/camelcase */ const result = await identity.getPendingInstructions(); - expect(result.length).toBe(2); + expect(result.length).toBe(3); expect(result[0].id).toEqual(id1); expect(result[1].id).toEqual(id2); + expect(result[2].id).toEqual(id4); }); }); }); diff --git a/src/api/procedures/__tests__/modifyInstructionAuthorization.ts b/src/api/procedures/__tests__/modifyInstructionAuthorization.ts index c2a972e513..73c5a06bb8 100644 --- a/src/api/procedures/__tests__/modifyInstructionAuthorization.ts +++ b/src/api/procedures/__tests__/modifyInstructionAuthorization.ts @@ -6,7 +6,7 @@ import { } from 'polymesh-types/types'; import sinon from 'sinon'; -import { Instruction } from '~/api/entities'; +import { DefaultPortfolio, Instruction } from '~/api/entities'; import { ModifyInstructionAuthorizationParams, prepareModifyInstructionAuthorization, @@ -19,6 +19,11 @@ import { AuthorizationStatus } from '~/types'; import { InstructionAuthorizationOperation, PortfolioId } from '~/types/internal'; import * as utilsModule from '~/utils'; +jest.mock( + '~/api/entities/Instruction', + require('~/testUtils/mocks/entities').mockInstructionModule('~/api/entities/Instruction') +); + describe('modifyInstructionAuthorization procedure', () => { const id = new BigNumber(1); const rawInstructionId = dsMockUtils.createMockU64(1); @@ -34,7 +39,6 @@ describe('modifyInstructionAuthorization procedure', () => { [MeshAuthorizationStatus], AuthorizationStatus >; - let instruction: Instruction; beforeAll(() => { dsMockUtils.initMocks({ @@ -46,11 +50,11 @@ describe('modifyInstructionAuthorization procedure', () => { entityMockUtils.initMocks(); numberToU64Stub = sinon.stub(utilsModule, 'numberToU64'); portfolioIdToMeshPortfolioIdStub = sinon.stub(utilsModule, 'portfolioIdToMeshPortfolioId'); + sinon.stub(utilsModule, 'portfolioLikeToPortfolioId'); meshAuthorizationStatusToAuthorizationStatusStub = sinon.stub( utilsModule, 'meshAuthorizationStatusToAuthorizationStatus' ); - instruction = new Instruction({ id }, mockContext); sinon.stub(procedureUtilsModule, 'assertInstructionValid'); }); @@ -58,6 +62,9 @@ describe('modifyInstructionAuthorization procedure', () => { let addTransactionStub: sinon.SinonStub; beforeEach(() => { + dsMockUtils.createTxStub('settlement', 'authorizeInstruction'); + dsMockUtils.createTxStub('settlement', 'unauthorizeInstruction'); + dsMockUtils.createTxStub('settlement', 'rejectInstruction'); addTransactionStub = procedureMockUtils.getAddTransactionStub(); mockContext = dsMockUtils.getContextInstance(); numberToU64Stub.returns(rawInstructionId); @@ -76,9 +83,11 @@ describe('modifyInstructionAuthorization procedure', () => { dsMockUtils.cleanup(); }); - test('should throw an error if operation is Authorize and the current status of the instruction is authorized', () => { + test("should throw an error if the operation is Authorize and all of the current Identity's Portfolios are authorized", () => { const rawAuthorizationStatus = dsMockUtils.createMockAuthorizationStatus('Authorized'); - dsMockUtils.createQueryStub('settlement', 'userAuths').resolves(rawAuthorizationStatus); + dsMockUtils.createQueryStub('settlement', 'userAuths', { + multi: [rawAuthorizationStatus, rawAuthorizationStatus], + }); meshAuthorizationStatusToAuthorizationStatusStub .withArgs(rawAuthorizationStatus) .returns(AuthorizationStatus.Authorized); @@ -97,7 +106,9 @@ describe('modifyInstructionAuthorization procedure', () => { test('should add an authorize instruction transaction to the queue', async () => { const rawAuthorizationStatus = dsMockUtils.createMockAuthorizationStatus('Pending'); - dsMockUtils.createQueryStub('settlement', 'userAuths').resolves(rawAuthorizationStatus); + dsMockUtils.createQueryStub('settlement', 'userAuths', { + multi: [rawAuthorizationStatus, rawAuthorizationStatus], + }); meshAuthorizationStatusToAuthorizationStatusStub .withArgs(rawAuthorizationStatus) .returns(AuthorizationStatus.Pending); @@ -115,14 +126,17 @@ describe('modifyInstructionAuthorization procedure', () => { sinon.assert.calledWith(addTransactionStub, transaction, {}, rawInstructionId, [ rawPortfolioId, + rawPortfolioId, ]); - expect(instruction.id).toEqual(result.id); + expect(result.id).toEqual(id); }); test('should throw an error if operation is Unauthorize and the current status of the instruction is pending', () => { const rawAuthorizationStatus = dsMockUtils.createMockAuthorizationStatus('Pending'); - dsMockUtils.createQueryStub('settlement', 'userAuths').resolves(rawAuthorizationStatus); + dsMockUtils.createQueryStub('settlement', 'userAuths', { + multi: [rawAuthorizationStatus, rawAuthorizationStatus], + }); meshAuthorizationStatusToAuthorizationStatusStub .withArgs(rawAuthorizationStatus) .returns(AuthorizationStatus.Pending); @@ -141,7 +155,9 @@ describe('modifyInstructionAuthorization procedure', () => { test('should throw an error if operation is Unauthorize and the current status of the instruction is rejected', () => { const rawAuthorizationStatus = dsMockUtils.createMockAuthorizationStatus('Rejected'); - dsMockUtils.createQueryStub('settlement', 'userAuths').resolves(rawAuthorizationStatus); + dsMockUtils.createQueryStub('settlement', 'userAuths', { + multi: [rawAuthorizationStatus, rawAuthorizationStatus], + }); meshAuthorizationStatusToAuthorizationStatusStub .withArgs(rawAuthorizationStatus) .returns(AuthorizationStatus.Rejected); @@ -160,7 +176,9 @@ describe('modifyInstructionAuthorization procedure', () => { test('should add an unauthorize instruction transaction to the queue', async () => { const rawAuthorizationStatus = dsMockUtils.createMockAuthorizationStatus('Authorized'); - dsMockUtils.createQueryStub('settlement', 'userAuths').resolves(rawAuthorizationStatus); + dsMockUtils.createQueryStub('settlement', 'userAuths', { + multi: [rawAuthorizationStatus, rawAuthorizationStatus], + }); meshAuthorizationStatusToAuthorizationStatusStub .withArgs(rawAuthorizationStatus) .returns(AuthorizationStatus.Authorized); @@ -178,14 +196,17 @@ describe('modifyInstructionAuthorization procedure', () => { sinon.assert.calledWith(addTransactionStub, transaction, {}, rawInstructionId, [ rawPortfolioId, + rawPortfolioId, ]); - expect(instruction.id).toEqual(result.id); + expect(result.id).toEqual(id); }); test('should throw an error if operation is Reject and the current status of the instruction is rejected', () => { const rawAuthorizationStatus = dsMockUtils.createMockAuthorizationStatus('Rejected'); - dsMockUtils.createQueryStub('settlement', 'userAuths').resolves(rawAuthorizationStatus); + dsMockUtils.createQueryStub('settlement', 'userAuths', { + multi: [rawAuthorizationStatus, rawAuthorizationStatus], + }); meshAuthorizationStatusToAuthorizationStatusStub .withArgs(rawAuthorizationStatus) .returns(AuthorizationStatus.Rejected); @@ -199,12 +220,33 @@ describe('modifyInstructionAuthorization procedure', () => { id, operation: InstructionAuthorizationOperation.Reject, }) - ).rejects.toThrow('The instruction cannot be rejected'); + ).rejects.toThrow('The Instruction cannot be rejected'); }); test('should add an reject instruction transaction to the queue', async () => { + const currentDid = (await mockContext.getCurrentIdentity()).did; + entityMockUtils.configureMocks({ + instructionOptions: { + getLegs: [ + { + from: new DefaultPortfolio({ did: 'notTheCurrentIdentity' }, mockContext), + to: new DefaultPortfolio({ did: currentDid }, mockContext), + token: entityMockUtils.getSecurityTokenInstance(), + amount: new BigNumber(100), + }, + { + from: new DefaultPortfolio({ did: currentDid }, mockContext), + to: new DefaultPortfolio({ did: 'notTheCurrentIdentity' }, mockContext), + token: entityMockUtils.getSecurityTokenInstance(), + amount: new BigNumber(200), + }, + ], + }, + }); const rawAuthorizationStatus = dsMockUtils.createMockAuthorizationStatus('Pending'); - dsMockUtils.createQueryStub('settlement', 'userAuths').resolves(rawAuthorizationStatus); + dsMockUtils.createQueryStub('settlement', 'userAuths', { + multi: [rawAuthorizationStatus, rawAuthorizationStatus], + }); meshAuthorizationStatusToAuthorizationStatusStub .withArgs(rawAuthorizationStatus) .returns(AuthorizationStatus.Pending); @@ -222,8 +264,9 @@ describe('modifyInstructionAuthorization procedure', () => { sinon.assert.calledWith(addTransactionStub, transaction, {}, rawInstructionId, [ rawPortfolioId, + rawPortfolioId, ]); - expect(instruction.id).toEqual(result.id); + expect(result.id).toEqual(id); }); }); diff --git a/src/api/procedures/addInstruction.ts b/src/api/procedures/addInstruction.ts index de78817112..a7e637648a 100644 --- a/src/api/procedures/addInstruction.ts +++ b/src/api/procedures/addInstruction.ts @@ -108,8 +108,13 @@ export async function prepareAddInstruction( await Promise.all( legs.map(async ({ from, to, amount, token }) => { - const { did: fromDid, number: fromNumber } = await portfolioLikeToPortfolioId(from, context); - const { did: toDid, number: toNumber } = await portfolioLikeToPortfolioId(to, context); + const [ + { did: fromDid, number: fromNumber }, + { did: toDid, number: toNumber }, + ] = await Promise.all([ + portfolioLikeToPortfolioId(from, context), + portfolioLikeToPortfolioId(to, context), + ]); const fromPortfolio = portfolioIdToMeshPortfolioId( { did: fromDid, number: fromNumber }, context diff --git a/src/api/procedures/modifyInstructionAuthorization.ts b/src/api/procedures/modifyInstructionAuthorization.ts index 48ac126a88..ea5299fb9e 100644 --- a/src/api/procedures/modifyInstructionAuthorization.ts +++ b/src/api/procedures/modifyInstructionAuthorization.ts @@ -1,14 +1,19 @@ +import { u64 } from '@polkadot/types'; import BigNumber from 'bignumber.js'; +import P from 'bluebird'; +import { AuthorizationStatus as MeshAuthorizationStatus, PortfolioId } from 'polymesh-types/types'; import { Instruction } from '~/api/entities'; import { assertInstructionValid } from '~/api/procedures/utils'; import { PolymeshError, Procedure } from '~/base'; import { AuthorizationStatus, ErrorCode } from '~/types'; -import { InstructionAuthorizationOperation } from '~/types/internal'; +import { InstructionAuthorizationOperation, PolymeshTx } from '~/types/internal'; +import { tuple } from '~/types/utils'; import { meshAuthorizationStatusToAuthorizationStatus, numberToU64, portfolioIdToMeshPortfolioId, + portfolioLikeToPortfolioId, } from '~/utils'; export interface ModifyInstructionAuthorizationParams { @@ -26,7 +31,7 @@ export async function prepareModifyInstructionAuthorization( const { context: { polymeshApi: { - tx, + tx: { settlement: settlementTx }, query: { settlement }, }, }, @@ -39,57 +44,81 @@ export async function prepareModifyInstructionAuthorization( await assertInstructionValid(instruction, context); - const currentIdentity = await context.getCurrentIdentity(); + const legs = await instruction.getLegs(); + const rawInstructionId = numberToU64(id, context); - const rawPortfolioId = portfolioIdToMeshPortfolioId({ did: currentIdentity.did }, context); - const rawAuthorizationStatus = await settlement.userAuths(rawPortfolioId, rawInstructionId); - const authorizationStatus = meshAuthorizationStatusToAuthorizationStatus(rawAuthorizationStatus); + const rawPortfolioIds: PortfolioId[] = []; + + const excludeCriteria: AuthorizationStatus[] = []; + let errorMessage: string; + let transaction: PolymeshTx<[u64, PortfolioId[]]>; switch (operation) { case InstructionAuthorizationOperation.Authorize: { - if (authorizationStatus === AuthorizationStatus.Authorized) { - throw new PolymeshError({ - code: ErrorCode.ValidationError, - message: 'The Instruction is already authorized', - }); - } - - this.addTransaction(tx.settlement.authorizeInstruction, {}, rawInstructionId, [ - rawPortfolioId, - ]); + excludeCriteria.push(AuthorizationStatus.Authorized); + errorMessage = 'The Instruction is already authorized'; + transaction = settlementTx.authorizeInstruction; break; } case InstructionAuthorizationOperation.Unauthorize: { - if ( - [AuthorizationStatus.Pending, AuthorizationStatus.Rejected].includes(authorizationStatus) - ) { - throw new PolymeshError({ - code: ErrorCode.ValidationError, - message: 'The instruction is not authorized', - }); - } - - this.addTransaction(tx.settlement.unauthorizeInstruction, {}, rawInstructionId, [ - rawPortfolioId, - ]); + excludeCriteria.push(AuthorizationStatus.Pending, AuthorizationStatus.Rejected); + errorMessage = 'The instruction is not authorized'; + transaction = settlementTx.unauthorizeInstruction; break; } case InstructionAuthorizationOperation.Reject: { - if (authorizationStatus === AuthorizationStatus.Rejected) { - throw new PolymeshError({ - code: ErrorCode.ValidationError, - message: 'The instruction cannot be rejected', - }); - } - - this.addTransaction(tx.settlement.rejectInstruction, {}, rawInstructionId, [rawPortfolioId]); + excludeCriteria.push(AuthorizationStatus.Rejected); + errorMessage = 'The Instruction cannot be rejected'; + transaction = settlementTx.rejectInstruction; break; } } + await Promise.all([ + P.map(legs, async ({ from, to }) => { + const [fromId, toId] = await Promise.all([ + portfolioLikeToPortfolioId(from, context), + portfolioLikeToPortfolioId(to, context), + ]); + + const [fromIsOwned, toIsOwned] = await Promise.all([from.isOwned(), to.isOwned()]); + + if (fromIsOwned) { + rawPortfolioIds.push(portfolioIdToMeshPortfolioId(fromId, context)); + } + + if (toIsOwned) { + rawPortfolioIds.push(portfolioIdToMeshPortfolioId(toId, context)); + } + }), + ]); + + const multiArgs = rawPortfolioIds.map(portfolioId => tuple(portfolioId, rawInstructionId)); + + const rawAuthorizationStatuses = await settlement.userAuths.multi( + multiArgs + ); + + const authorizationStatuses = rawAuthorizationStatuses.map( + meshAuthorizationStatusToAuthorizationStatus + ); + + const validPortfolioIds = rawPortfolioIds.filter( + (_, index) => !excludeCriteria.includes(authorizationStatuses[index]) + ); + + if (!validPortfolioIds.length) { + throw new PolymeshError({ + code: ErrorCode.ValidationError, + message: errorMessage, + }); + } + + this.addTransaction(transaction, {}, rawInstructionId, validPortfolioIds); + return instruction; } diff --git a/src/base/Context.ts b/src/base/Context.ts index f4d076241b..022ca85cd7 100644 --- a/src/base/Context.ts +++ b/src/base/Context.ts @@ -578,6 +578,7 @@ export class Context { * @param opts.targets - identities (or Identity IDs) for which to fetch claims (targets). Defaults to all targets * @param opts.trustedClaimIssuers - identity IDs of claim issuers. Defaults to all claim issuers * @param opts.claimTypes - types of the claims to fetch. Defaults to any type + * @param opts.includeExpired - whether to include expired claims. Defaults to true * @param opts.size - page size * @param opts.start - page offset * @@ -593,7 +594,7 @@ export class Context { start?: number; } = {} ): Promise> { - const { targets, trustedClaimIssuers, claimTypes, includeExpired, size, start } = opts; + const { targets, trustedClaimIssuers, claimTypes, includeExpired = true, size, start } = opts; const result = await this.queryMiddleware>( didsWithClaims({ diff --git a/src/base/__tests__/Context.ts b/src/base/__tests__/Context.ts index 6707be39f3..786cff62be 100644 --- a/src/base/__tests__/Context.ts +++ b/src/base/__tests__/Context.ts @@ -1168,7 +1168,7 @@ describe('Context class', () => { dids: undefined, trustedClaimIssuers: undefined, claimTypes: undefined, - includeExpired: undefined, + includeExpired: true, count: undefined, skip: undefined, }), @@ -1343,7 +1343,7 @@ describe('Context class', () => { dids: undefined, trustedClaimIssuers: undefined, claimTypes: undefined, - includeExpired: undefined, + includeExpired: true, count: undefined, skip: undefined, }), diff --git a/src/testUtils/mocks/entities.ts b/src/testUtils/mocks/entities.ts index 94afd4305a..02923ab30f 100644 --- a/src/testUtils/mocks/entities.ts +++ b/src/testUtils/mocks/entities.ts @@ -28,6 +28,7 @@ import { InstructionDetails, InstructionStatus, InstructionType, + Leg, PortfolioBalance, SecondaryKey, SecurityTokenDetails, @@ -134,6 +135,7 @@ interface NumberedPortfolioOptions { interface InstructionOptions { id?: BigNumber; details?: Partial; + getLegs?: Leg[]; } let identityConstructorStub: SinonStub; @@ -171,6 +173,7 @@ let currentAccountGetTransactionHistoryStub: SinonStub; let tickerReservationDetailsStub: SinonStub; let venueDetailsStub: SinonStub; let instructionDetailsStub: SinonStub; +let instructionGetLegsStub: SinonStub; let numberedPortfolioIsOwnedStub: SinonStub; let numberedPortfolioGetTokenBalancesStub: SinonStub; @@ -675,8 +678,17 @@ function initIdentity(opts?: IdentityOptions): void { */ function configureInstruction(opts: InstructionOptions): void { const details = { venue: mockInstanceContainer.venue, ...opts.details }; + const legs = opts.getLegs || [ + { + from: mockInstanceContainer.numberedPortfolio, + to: mockInstanceContainer.numberedPortfolio, + token: mockInstanceContainer.securityToken, + amount: new BigNumber(100), + }, + ]; const instruction = ({ details: instructionDetailsStub.resolves(details), + getLegs: instructionGetLegsStub.resolves(legs), } as unknown) as MockInstruction; Object.assign(mockInstanceContainer.instruction, instruction); @@ -692,6 +704,7 @@ function configureInstruction(opts: InstructionOptions): void { function initInstruction(opts?: InstructionOptions): void { instructionConstructorStub = sinon.stub(); instructionDetailsStub = sinon.stub(); + instructionGetLegsStub = sinon.stub(); instructionOptions = { ...defaultInstructionOptions, ...opts };