generated from PolymeshAssociation/typescript-boilerplate
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement venue and instruction entities
- Loading branch information
Showing
17 changed files
with
793 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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), | ||
}; | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
Oops, something went wrong.