Skip to content

Commit

Permalink
feat(checkpoints): add Checkpoints.get and create Checkpoint entity
Browse files Browse the repository at this point in the history
Added `totalSupply` and `timestamp` to Checkpoint
  • Loading branch information
monitz87 committed Feb 24, 2021
1 parent 6b0ac85 commit e5cf565
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 18 deletions.
12 changes: 6 additions & 6 deletions src/Claims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ export class Claims {
*
* @param args.claims - array of claims to be added
*
* @note required role if at least one claim is CDD type:
* - Customer Due Diligence Provider
* @note required roles:
* - Customer Due Diligence Provider: if there is at least one CDD claim in the arguments
*/
public addClaims: ProcedureMethod<Pick<ModifyClaimsParams, 'claims'>, void>;

Expand All @@ -116,8 +116,8 @@ export class Claims {
*
* @param args.claims - array of claims to be edited
*
* @note required role if at least one claim is CDD type:
* - Customer Due Diligence Provider
* @note required roles:
* - Customer Due Diligence Provider: if there is at least one CDD claim in the arguments
*/

public editClaims: ProcedureMethod<Pick<ModifyClaimsParams, 'claims'>, void>;
Expand All @@ -127,8 +127,8 @@ export class Claims {
*
* @param args.claims - array of claims to be revoked
*
* @note required role if at least one claim is CDD type:
* - Customer Due Diligence Provider
* @note required roles:
* - Customer Due Diligence Provider: if there is at least one CDD claim in the arguments
*/
public revokeClaims: ProcedureMethod<Pick<ModifyClaimsParams, 'claims'>, void>;

Expand Down
2 changes: 1 addition & 1 deletion src/Polymesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { TxTag } from 'polymesh-types/types';
import {
Account,
Context,
CurrentAccount,
Identity,
PolymeshError,
registerIdentity,
Expand All @@ -28,7 +29,6 @@ import { heartbeat } from '~/middleware/queries';
import {
AccountBalance,
CommonKeyring,
CurrentAccount,
CurrentIdentity,
ErrorCode,
MiddlewareConfig,
Expand Down
75 changes: 75 additions & 0 deletions src/api/entities/Checkpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import BigNumber from 'bignumber.js';

import { Context, Entity } from '~/internal';
import { balanceToBigNumber, momentToDate, numberToU64, stringToTicker } from '~/utils/conversion';

export interface UniqueIdentifiers {
id: BigNumber;
ticker: string;
}

/**
* Represents a snapshot of the Security Token's holders and their respective balances
* at a certain point in time
*/
export class Checkpoint extends Entity<UniqueIdentifiers> {
/**
* @hidden
* Check if a value is of type [[UniqueIdentifiers]]
*/
public static isUniqueIdentifiers(identifier: unknown): identifier is UniqueIdentifiers {
const { id, ticker } = identifier as UniqueIdentifiers;

return id instanceof BigNumber && typeof ticker === 'string';
}

/**
* checkpoint identifier number
*/
public id: BigNumber;

/**
* ticker of the Security Token whose balances are being recorded
*/
public ticker: string;

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

const { id, ticker } = identifiers;

this.id = id;
this.ticker = ticker;
}

/**
* Retrieve the Security Token's total supply at this checkpoint
*/
public async totalSupply(): Promise<BigNumber> {
const { context, ticker, id } = this;

const rawSupply = await context.polymeshApi.query.checkpoint.totalSupply(
stringToTicker(ticker, context),
numberToU64(id, context)
);

return balanceToBigNumber(rawSupply);
}

/**
* Retrieve this Checkpoint's creation date
*/
public async createdAt(): Promise<Date> {
const { context, ticker, id } = this;

const creationTime = await context.polymeshApi.query.checkpoint.timestamps(
stringToTicker(ticker, context),
numberToU64(id, context)
);

return momentToDate(creationTime);
}
}
40 changes: 38 additions & 2 deletions src/api/entities/SecurityToken/Checkpoints.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Context, createCheckpoint, Namespace, SecurityToken } from '~/internal';
import { Checkpoint, Context, createCheckpoint, Namespace, SecurityToken } from '~/internal';
import { CheckpointWithCreationDate, PaginationOptions, ResultSet } from '~/types';
import { ProcedureMethod } from '~/types/internal';
import { createProcedureMethod } from '~/utils/internal';
import { momentToDate, stringToTicker, u64ToBigNumber } from '~/utils/conversion';
import { createProcedureMethod, requestPaginated } from '~/utils/internal';

/**
* Handles all Security Token Checkpoints related functionality
Expand All @@ -24,4 +26,38 @@ export class Checkpoints extends Namespace<SecurityToken> {
* - Security Token Owner
*/
public create: ProcedureMethod<void, SecurityToken>;

/**
* Retrieve all Checkpoints created on this Security Token, together with their corresponding creation Date
*
* @note supports pagination
*/
public async get(
paginationOpts?: PaginationOptions
): Promise<ResultSet<CheckpointWithCreationDate>> {
const {
parent: { ticker },
context,
} = this;

const rawTicker = stringToTicker(ticker, context);

const { entries, lastKey: next } = await requestPaginated(
context.polymeshApi.query.checkpoint.timestamps,
{ paginationOpts, arg: rawTicker }
);

const now = new Date();
const data = entries
.map(([{ args: [, id] }, timestamp]) => ({
checkpoint: new Checkpoint({ id: u64ToBigNumber(id), ticker }, context),
createdAt: momentToDate(timestamp),
}))
.filter(({ createdAt }) => createdAt <= now);

return {
data,
next,
};
}
}
66 changes: 57 additions & 9 deletions src/api/entities/SecurityToken/__tests__/Checkpoints.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
import BigNumber from 'bignumber.js';
import sinon from 'sinon';

import { createCheckpoint, Namespace, SecurityToken, TransactionQueue } from '~/internal';
import { Context, createCheckpoint, Namespace, SecurityToken, TransactionQueue } from '~/internal';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { tuple } from '~/types/utils';
import * as utilsConversionModule from '~/utils/conversion';

import { Checkpoints } from '../Checkpoints';

jest.mock(
'~/api/entities/Checkpoint',
require('~/testUtils/mocks/entities').mockCheckpointModule('~/api/entities/Checkpoint')
);

describe('Checkpoints class', () => {
let context: Context;
let checkpoints: Checkpoints;

let ticker: string;

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

ticker = 'SOME_TICKER';
});

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

context = dsMockUtils.getContextInstance();

const token = entityMockUtils.getSecurityTokenInstance({ ticker });
checkpoints = new Checkpoints(token, context);
});

afterAll(() => {
Expand All @@ -31,20 +51,48 @@ describe('Checkpoints class', () => {
});

test('should prepare the procedure with the correct arguments and context, and return the resulting transaction queue', async () => {
const context = dsMockUtils.getContextInstance();
const token = entityMockUtils.getSecurityTokenInstance();
const checkpoints = new Checkpoints(token, context);

const expectedQueue = ('someQueue' as unknown) as TransactionQueue<SecurityToken>;

sinon
.stub(createCheckpoint, 'prepare')
.withArgs({ ticker: token.ticker }, context)
.resolves(expectedQueue);
sinon.stub(createCheckpoint, 'prepare').withArgs({ ticker }, context).resolves(expectedQueue);

const queue = await checkpoints.create();

expect(queue).toBe(expectedQueue);
});
});

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

test('should return all created checkpoints with their timestamps', async () => {
const timestamps = [1000, 2000, new Date().getTime() + 10000];
const ids = [1, 2, 3];
const rawTicker = dsMockUtils.createMockTicker(ticker);

dsMockUtils.createQueryStub('checkpoint', 'timestamps', {
entries: timestamps.map((timestamp, index) =>
tuple(
[rawTicker, dsMockUtils.createMockU64(ids[index])],
dsMockUtils.createMockMoment(timestamp)
)
),
});

sinon
.stub(utilsConversionModule, 'stringToTicker')
.withArgs(ticker, context)
.returns(rawTicker);

const { data } = await checkpoints.get();

expect(data).toEqual(
timestamps.slice(0, -1).map((timestamp, index) => ({
checkpoint: entityMockUtils.getCheckpointInstance({ id: new BigNumber(ids[index]) }),
createdAt: new Date(timestamp),
}))
);
});
});
});
82 changes: 82 additions & 0 deletions src/api/entities/__tests__/Checkpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import BigNumber from 'bignumber.js';

import { Checkpoint, Context, Entity } from '~/internal';
import { dsMockUtils } from '~/testUtils/mocks';

describe('Checkpoint class', () => {
let context: Context;

let id: BigNumber;
let ticker: string;

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

id = new BigNumber(1);
ticker = 'SOME_TICKER';
});

beforeEach(() => {
context = dsMockUtils.getContextInstance();
});

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

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

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

describe('constructor', () => {
test('should assign ticker and id to instance', () => {
const checkpoint = new Checkpoint({ id, ticker }, context);

expect(checkpoint.ticker).toBe(ticker);
expect(checkpoint.id).toEqual(id);
});
});

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

describe('method: createdAt', () => {
test("should return the Checkpoint's creation date", async () => {
const checkpoint = new Checkpoint({ id, ticker }, context);
const timestamp = 12000;

dsMockUtils.createQueryStub('checkpoint', 'timestamps', {
returnValue: dsMockUtils.createMockMoment(timestamp),
});

const result = await checkpoint.createdAt();

expect(result).toEqual(new Date(timestamp));
});
});

describe('method: totalSupply', () => {
test("should return the Checkpoint's total supply", async () => {
const checkpoint = new Checkpoint({ id, ticker }, context);
const balance = 10000000000;

dsMockUtils.createQueryStub('checkpoint', 'totalSupply', {
returnValue: dsMockUtils.createMockBalance(balance),
});

const result = await checkpoint.totalSupply();

expect(result).toEqual(new BigNumber(balance).shiftedBy(-6));
});
});
});
1 change: 1 addition & 0 deletions src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,4 @@ export { Portfolio } from '~/api/entities/Portfolio';
export { DefaultPortfolio } from '~/api/entities/DefaultPortfolio';
export { NumberedPortfolio } from '~/api/entities/NumberedPortfolio';
export { TransactionQueue } from '~/base/TransactionQueue';
export { Checkpoint } from '~/api/entities/Checkpoint';

0 comments on commit e5cf565

Please sign in to comment.