Skip to content

Commit

Permalink
feat: add PortfolioCustodian as new Role
Browse files Browse the repository at this point in the history
  • Loading branch information
shuffledex committed Nov 13, 2020
1 parent ba69f96 commit aaed57b
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 3 deletions.
59 changes: 58 additions & 1 deletion src/api/entities/Identity/__tests__/index.ts
Expand Up @@ -8,7 +8,15 @@ import { Entity, Identity } from '~/api/entities';
import { Context } from '~/base';
import { tokensByTrustedClaimIssuer, tokensHeldByDid } from '~/middleware/queries';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { Order, Role, RoleType, TickerOwnerRole, TokenOwnerRole, VenueOwnerRole } from '~/types';
import {
Order,
PortfolioCustodianRole,
Role,
RoleType,
TickerOwnerRole,
TokenOwnerRole,
VenueOwnerRole,
} from '~/types';
import * as utilsModule from '~/utils';

jest.mock(
Expand All @@ -25,6 +33,18 @@ jest.mock(
'~/api/entities/Venue',
require('~/testUtils/mocks/entities').mockVenueModule('~/api/entities/Venue')
);
jest.mock(
'~/api/entities/NumberedPortfolio',
require('~/testUtils/mocks/entities').mockNumberedPortfolioModule(
'~/api/entities/NumberedPortfolio'
)
);
jest.mock(
'~/api/entities/DefaultPortfolio',
require('~/testUtils/mocks/entities').mockDefaultPortfolioModule(
'~/api/entities/DefaultPortfolio'
)
);

describe('Identity class', () => {
let context: Context;
Expand Down Expand Up @@ -154,6 +174,43 @@ describe('Identity class', () => {
expect(hasRole).toBe(false);
});

test('hasRole should check whether the Identity has the Portfolio Custodian role', async () => {
const did = 'someDid';
const identity = new Identity({ did }, context);
const portfolioId = {
did,
number: new BigNumber(1),
};
let role: PortfolioCustodianRole = { type: RoleType.PortfolioCustodian, portfolioId };

entityMockUtils.configureMocks({
numberedPortfolioOptions: { custodian: new Identity({ did }, context) },
defaultPortfolioOptions: { custodian: new Identity({ did }, context) },
});

let hasRole = await identity.hasRole(role);

expect(hasRole).toBe(true);

identity.did = 'otherDid';

hasRole = await identity.hasRole(role);

expect(hasRole).toBe(false);

role = { type: RoleType.PortfolioCustodian, portfolioId: { did } };

hasRole = await identity.hasRole(role);

expect(hasRole).toBe(false);

identity.did = did;

hasRole = await identity.hasRole(role);

expect(hasRole).toBe(true);
});

test('hasRole should throw an error if the role is not recognized', () => {
const identity = new Identity({ did: 'someDid' }, context);
const type = 'Fake' as RoleType;
Expand Down
26 changes: 25 additions & 1 deletion src/api/entities/Identity/index.ts
Expand Up @@ -2,14 +2,22 @@ import { u64 } from '@polkadot/types';
import { BigNumber } from 'bignumber.js';
import { CddStatus, DidRecord } from 'polymesh-types/types';

import { Entity, SecurityToken, TickerReservation, Venue } from '~/api/entities';
import {
DefaultPortfolio,
Entity,
NumberedPortfolio,
SecurityToken,
TickerReservation,
Venue,
} from '~/api/entities';
import { Context, PolymeshError } from '~/base';
import { tokensByTrustedClaimIssuer, tokensHeldByDid } from '~/middleware/queries';
import { Query } from '~/middleware/types';
import {
Ensured,
ErrorCode,
isCddProviderRole,
isPortfolioCustodianRole,
isTickerOwnerRole,
isTokenOwnerRole,
isVenueOwnerRole,
Expand Down Expand Up @@ -114,6 +122,22 @@ export class Identity extends Entity<UniqueIdentifiers> {
const { owner } = await venue.details();

return owner.did === did;
} else if (isPortfolioCustodianRole(role)) {
const {
portfolioId: { did: portfolioDid, number },
} = role;

let portfolio;

if (number) {
portfolio = new NumberedPortfolio({ did: portfolioDid, id: number }, context);
} else {
portfolio = new DefaultPortfolio({ did: portfolioDid }, context);
}

const custodianIdentity = await portfolio.getCustodian();

return custodianIdentity.did === did;
}

throw new PolymeshError({
Expand Down
11 changes: 11 additions & 0 deletions src/testUtils/mocks/entities.ts
Expand Up @@ -20,6 +20,7 @@ import {
Venue,
} from '~/api/entities';
import { ProposalDetails, ProposalStage /*, ProposalState */ } from '~/api/entities/Proposal/types';
import { entityMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import {
AccountBalance,
Expand Down Expand Up @@ -134,12 +135,14 @@ interface NumberedPortfolioOptions {
tokenBalances?: PortfolioBalance[];
did?: string;
exists?: boolean;
custodian?: Identity;
}

interface DefaultPortfolioOptions {
isOwnedBy?: boolean;
tokenBalances?: PortfolioBalance[];
did?: string;
custodian?: Identity;
}

interface InstructionOptions {
Expand Down Expand Up @@ -187,8 +190,10 @@ let instructionGetLegsStub: SinonStub;
let numberedPortfolioIsOwnedByStub: SinonStub;
let numberedPortfolioGetTokenBalancesStub: SinonStub;
let numberedPortfolioExistsStub: SinonStub;
let numberedPortfolioGetCustodianStub: SinonStub;
let defaultPortfolioIsOwnedByStub: SinonStub;
let defaultPortfolioGetTokenBalancesStub: SinonStub;
let defaultPortfolioGetCustodianStub: SinonStub;

const MockIdentityClass = class {
/**
Expand Down Expand Up @@ -438,6 +443,7 @@ const defaultNumberedPortfolioOptions: NumberedPortfolioOptions = {
],
did: 'someDid',
exists: true,
custodian: {} as MockIdentity,
};
let numberedPortfolioOptions = defaultNumberedPortfolioOptions;
const defaultDefaultPortfolioOptions: DefaultPortfolioOptions = {
Expand All @@ -450,6 +456,7 @@ const defaultDefaultPortfolioOptions: DefaultPortfolioOptions = {
},
],
did: 'someDid',
custodian: {} as MockIdentity,
};
let defaultPortfolioOptions = defaultDefaultPortfolioOptions;
const defaultInstructionOptions: InstructionOptions = {
Expand Down Expand Up @@ -551,6 +558,7 @@ function configureNumberedPortfolio(opts: NumberedPortfolioOptions): void {
getTokenBalances: numberedPortfolioGetTokenBalancesStub.resolves(opts.tokenBalances),
owner: { did: opts.did },
exists: numberedPortfolioExistsStub.resolves(opts.exists),
getCustodian: numberedPortfolioGetCustodianStub.resolves(opts.custodian),
} as unknown) as MockNumberedPortfolio;

Object.assign(mockInstanceContainer.numberedPortfolio, numberedPortfolio);
Expand All @@ -573,6 +581,7 @@ function initNumberedPortfolio(opts?: NumberedPortfolioOptions): void {
numberedPortfolioIsOwnedByStub = sinon.stub();
numberedPortfolioGetTokenBalancesStub = sinon.stub();
numberedPortfolioExistsStub = sinon.stub();
numberedPortfolioGetCustodianStub = sinon.stub();

numberedPortfolioOptions = { ...defaultNumberedPortfolioOptions, ...opts };

Expand All @@ -588,6 +597,7 @@ function configureDefaultPortfolio(opts: DefaultPortfolioOptions): void {
isOwnedBy: defaultPortfolioIsOwnedByStub.resolves(opts.isOwnedBy),
getTokenBalances: defaultPortfolioGetTokenBalancesStub.resolves(opts.tokenBalances),
owner: { did: opts.did },
getCustodian: defaultPortfolioGetCustodianStub.resolves(opts.custodian),
} as unknown) as MockDefaultPortfolio;

Object.assign(mockInstanceContainer.defaultPortfolio, defaultPortfolio);
Expand All @@ -609,6 +619,7 @@ function initDefaultPortfolio(opts?: DefaultPortfolioOptions): void {
defaultPortfolioConstructorStub = sinon.stub();
defaultPortfolioIsOwnedByStub = sinon.stub();
defaultPortfolioGetTokenBalancesStub = sinon.stub();
defaultPortfolioGetCustodianStub = sinon.stub();

defaultPortfolioOptions = { ...defaultDefaultPortfolioOptions, ...opts };

Expand Down
21 changes: 20 additions & 1 deletion src/types/index.ts
Expand Up @@ -14,6 +14,7 @@ import {
} from '~/api/entities';
// import { ProposalDetails } from '~/api/entities/Proposal/types';
import { CountryCode } from '~/generated/types';
import { PortfolioId } from '~/types/internal';

export * from '~/generated/types';

Expand Down Expand Up @@ -78,6 +79,7 @@ export enum RoleType {
TokenOwner = 'TokenOwner',
CddProvider = 'CddProvider',
VenueOwner = 'VenueOwner',
PortfolioCustodian = 'PortfolioCustodian',
}

export interface TickerOwnerRole {
Expand Down Expand Up @@ -127,7 +129,24 @@ export function isVenueOwnerRole(role: Role): role is VenueOwnerRole {
return role.type === RoleType.VenueOwner;
}

export type Role = TickerOwnerRole | TokenOwnerRole | CddProviderRole | VenueOwnerRole;
export interface PortfolioCustodianRole {
type: RoleType.PortfolioCustodian;
portfolioId: PortfolioId;
}

/**
* @hidden
*/
export function isPortfolioCustodianRole(role: Role): role is PortfolioCustodianRole {
return role.type === RoleType.PortfolioCustodian;
}

export type Role =
| TickerOwnerRole
| TokenOwnerRole
| CddProviderRole
| VenueOwnerRole
| PortfolioCustodianRole;

export enum KnownTokenType {
EquityCommon = 'EquityCommon',
Expand Down

0 comments on commit aaed57b

Please sign in to comment.