Skip to content

Commit

Permalink
feat: implement venue and instruction entities
Browse files Browse the repository at this point in the history
  • Loading branch information
monitz87 committed Oct 5, 2020
1 parent 0dfb8ff commit 0a12b2a
Show file tree
Hide file tree
Showing 17 changed files with 793 additions and 20 deletions.
172 changes: 172 additions & 0 deletions src/api/entities/Instruction/__tests__/index.ts
@@ -0,0 +1,172 @@
import { u64 } from '@polkadot/types';
import BigNumber from 'bignumber.js';
import sinon from 'sinon';

import { Entity, Instruction } from '~/api/entities';
import { Context } from '~/base';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { InstructionStatus, InstructionType } from '~/types';
import { tuple } from '~/types/utils';
import * as utilsModule from '~/utils';

jest.mock(
'~/api/entities/Identity',
require('~/testUtils/mocks/entities').mockIdentityModule('~/api/entities/Identity')
);
jest.mock(
'~/api/entities/SecurityToken',
require('~/testUtils/mocks/entities').mockSecurityTokenModule('~/api/entities/SecurityToken')
);

describe('Instruction class', () => {
let context: Mocked<Context>;
let instruction: Instruction;

let id: BigNumber;

let rawId: u64;

beforeAll(() => {
dsMockUtils.initMocks();
entityMockUtils.initMocks();

id = new BigNumber(1);
rawId = dsMockUtils.createMockU64(id.toNumber());
});

beforeEach(() => {
context = dsMockUtils.getContextInstance();
instruction = new Instruction({ id }, context);
});

afterEach(() => {
dsMockUtils.reset();
entityMockUtils.reset();
});

afterAll(() => {
dsMockUtils.cleanup();
entityMockUtils.cleanup();
});

test('should extend Entity', () => {
expect(Instruction.prototype instanceof Entity).toBe(true);
});

describe('method: isUniqueIdentifiers', () => {
test('should return true if the object conforms to the interface', () => {
expect(Instruction.isUniqueIdentifiers({ id: new BigNumber(1) })).toBe(true);
expect(Instruction.isUniqueIdentifiers({})).toBe(false);
expect(Instruction.isUniqueIdentifiers({ id: 3 })).toBe(false);
});
});

describe('method: details', () => {
afterAll(() => {
sinon.restore();
});

test('should return the Instruction details', async () => {
const status = InstructionStatus.Pending;
const createdAt = new Date('10/14/1987');
const validFrom = new Date('11/17/1987');
let type = InstructionType.SettleOnAuthorization;
const creator = 'someDid';

entityMockUtils.configureMocks({ identityOptions: { did: creator } });
sinon
.stub(utilsModule, 'numberToU64')
.withArgs(id, context)
.returns(rawId);

const queryResult = {
status: dsMockUtils.createMockInstructionStatus(status),
/* eslint-disable @typescript-eslint/camelcase */
created_at: dsMockUtils.createMockOption(dsMockUtils.createMockMoment(createdAt.getTime())),
valid_from: dsMockUtils.createMockOption(dsMockUtils.createMockMoment(validFrom.getTime())),
settlement_type: dsMockUtils.createMockSettlementType(type),
/* eslint-enable @typescript-eslint/camelcase */
};

const instructionDetailsStub = dsMockUtils
.createQueryStub('settlement', 'instructionDetails')
.withArgs(rawId)
.resolves(queryResult);

let result = await instruction.details();

expect(result).toEqual({
status,
createdAt,
validFrom,
type,
});

type = InstructionType.SettleOnBlock;
const endBlock = new BigNumber(100);

instructionDetailsStub.resolves({
...queryResult,
// eslint-disable-next-line @typescript-eslint/camelcase
settlement_type: dsMockUtils.createMockSettlementType({
SettleOnBlock: dsMockUtils.createMockU32(endBlock.toNumber()),
}),
});

result = await instruction.details();

expect(result).toEqual({
status,
createdAt,
validFrom,
type,
endBlock,
});
});
});

describe('method: getLegs', () => {
afterAll(() => {
sinon.restore();
});

test("should return the instruction's legs", async () => {
const identityConstructor = entityMockUtils.getIdentityConstructorStub();
const fromDid = 'fromDid';
const toDid = 'toDid';
const ticker = 'SOMETICKER';
const amount = new BigNumber(1000);

entityMockUtils.configureMocks({ securityTokenOptions: { ticker } });
sinon
.stub(utilsModule, 'numberToU64')
.withArgs(id, context)
.returns(rawId);
dsMockUtils.createQueryStub('settlement', 'instructionLegs', {
entries: [
tuple(['instructionId', 'legId'], {
from: dsMockUtils.createMockPortfolioId({
did: dsMockUtils.createMockIdentityId(fromDid),
kind: dsMockUtils.createMockPortfolioKind('Default'),
}),
to: dsMockUtils.createMockPortfolioId({
did: dsMockUtils.createMockIdentityId(toDid),
kind: dsMockUtils.createMockPortfolioKind('Default'),
}),
asset: dsMockUtils.createMockTicker(ticker),
amount: dsMockUtils.createMockBalance(amount.times(Math.pow(10, 6)).toNumber()),
}),
],
});

const [leg] = await instruction.getLegs();

sinon.assert.calledTwice(identityConstructor);
sinon.assert.calledWithExactly(identityConstructor.firstCall, { did: fromDid }, context);
sinon.assert.calledWithExactly(identityConstructor.secondCall, { did: toDid }, context);
expect(leg.amount).toEqual(amount);
expect(leg.token).toEqual(entityMockUtils.getSecurityTokenInstance());
});
});
});
123 changes: 123 additions & 0 deletions src/api/entities/Instruction/index.ts
@@ -0,0 +1,123 @@
import BigNumber from 'bignumber.js';

import { Entity, Identity, SecurityToken } from '~/api/entities';
import { Context } from '~/base';
import {
balanceToBigNumber,
identityIdToString,
meshInstructionStatusToInstructionStatus,
momentToDate,
numberToU64,
tickerToString,
u32ToBigNumber,
} from '~/utils';

import { InstructionDetails, InstructionType, Leg } from './types';

export interface UniqueIdentifiers {
id: BigNumber;
}

/**
* Represents a trusted claim issuer for a specific token in the Polymesh blockchain
*/
export class Instruction extends Entity<UniqueIdentifiers> {
/**
* @hidden
* Check if a value is of type [[UniqueIdentifiers]]
*/
public static isUniqueIdentifiers(identifier: unknown): identifier is UniqueIdentifiers {
const { id } = identifier as UniqueIdentifiers;

return id instanceof BigNumber;
}

/**
* Identifier number of the venue
*/
public id: BigNumber;

/**
* @hidden
*/
public constructor(identifiers: UniqueIdentifiers, context: Context) {
super(identifiers, context);

const { id } = identifiers;

this.id = id;
}

/**
* Retrieve information specific to this Instruction
*/
public async details(): Promise<InstructionDetails> {
const {
context: {
polymeshApi: {
query: { settlement },
},
},
id,
context,
} = this;

const {
status,
created_at: createdAt,
valid_from: validFrom,
settlement_type: type,
} = await settlement.instructionDetails(numberToU64(id, context));

const details = {
status: meshInstructionStatusToInstructionStatus(status),
createdAt: momentToDate(createdAt.unwrap()), // NOTE @monitz87: I'm pretty sure neither of these two can be null, but I'll ask
validFrom: momentToDate(validFrom.unwrap()),
};

if (type.isSettleOnAuthorization) {
return {
...details,
type: InstructionType.SettleOnAuthorization,
};
}

return {
...details,
type: InstructionType.SettleOnBlock,
endBlock: u32ToBigNumber(type.asSettleOnBlock),
};
}

/**
* Retrieve all legs of this Instruction
*/
public async getLegs(): Promise<Leg[]> {
const {
context: {
polymeshApi: {
query: { settlement },
},
},
id,
context,
} = this;

const legs = await settlement.instructionLegs.entries(numberToU64(id, context));

return legs.map(([, leg]) => {
const { from, to, amount, asset } = leg;

const ticker = tickerToString(asset);
const fromDid = identityIdToString(from.did);
const toDid = identityIdToString(to.did);

return {
from: new Identity({ did: fromDid }, context),
to: new Identity({ did: toDid }, context),
amount: balanceToBigNumber(amount),
token: new SecurityToken({ ticker }, context),
};
});
}
}
34 changes: 34 additions & 0 deletions src/api/entities/Instruction/types.ts
@@ -0,0 +1,34 @@
import BigNumber from 'bignumber.js';

import { Identity, SecurityToken } from '~/api/entities';

export enum InstructionStatus {
Pending = 'Pending',
Unknown = 'Unknown',
}

export enum InstructionType {
SettleOnAuthorization = 'SettleOnAuthorization',
SettleOnBlock = 'SettleOnBlock',
}

export type InstructionDetails = {
status: InstructionStatus;
createdAt: Date;
validFrom: Date;
} & (
| {
type: InstructionType.SettleOnAuthorization;
}
| {
type: InstructionType.SettleOnBlock;
endBlock: BigNumber;
}
);

export interface Leg {
from: Identity; // NOTE @monitz87: change these identities to portfolios when the entity exists
to: Identity;
amount: BigNumber;
token: SecurityToken;
}

0 comments on commit 0a12b2a

Please sign in to comment.