Skip to content

Commit

Permalink
feat: allow setting a CA's checkpoint to null
Browse files Browse the repository at this point in the history
This was made possible by refactoring the CA classes into a base abstract class and two
implementations, allowing `CorporateAction` and `DividendDistribution` to have separate
signatures for `modifyCheckpoint`

BREAKING CHANGE: change the type of `ModifyCheckpointParams.checkpoint` to
  `Checkpoint | CheckpointSchedule | Date | null`
  • Loading branch information
monitz87 committed Oct 4, 2021
1 parent 641e58e commit 845ebc2
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 61 deletions.
64 changes: 64 additions & 0 deletions src/api/entities/CorporateAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import BigNumber from 'bignumber.js';

import { CorporateActionBase } from '~/api/entities/CorporateActionBase';
import { Context, modifyCaCheckpoint, ModifyCaCheckpointParams } from '~/internal';
import {
CorporateActionKind,
CorporateActionTargets,
ProcedureMethod,
TaxWithholding,
} from '~/types';
import { HumanReadableType } from '~/types/utils';
import { createProcedureMethod } from '~/utils/internal';

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

export interface HumanReadable {
id: string;
ticker: string;
declarationDate: string;
description: string;
targets: HumanReadableType<CorporateActionTargets>;
defaultTaxWithholding: string;
taxWithholdings: HumanReadableType<TaxWithholding[]>;
}

export interface Params {
kind: CorporateActionKind;
declarationDate: Date;
description: string;
targets: CorporateActionTargets;
defaultTaxWithholding: BigNumber;
taxWithholdings: TaxWithholding[];
}

/**
* Represents an action initiated by the issuer of a Security Token which may affect the positions of
* the Tokenholders
*/
export class CorporateAction extends CorporateActionBase {
/**
* @hidden
*/
public constructor(args: UniqueIdentifiers & Params, context: Context) {
super(args, context);

this.modifyCheckpoint = createProcedureMethod(
{
getProcedureAndArgs: modifyCaCheckpointArgs => [
modifyCaCheckpoint,
{ corporateAction: this, ...modifyCaCheckpointArgs },
],
},
context
);
}

/**
* Modify the Corporate Action's Checkpoint
*/
public modifyCheckpoint: ProcedureMethod<ModifyCaCheckpointParams, void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Checkpoint,
CheckpointSchedule,
Context,
CorporateAction,
CorporateActionBase,
Entity,
TransactionQueue,
} from '~/internal';
Expand Down Expand Up @@ -44,10 +44,16 @@ describe('CorporateAction class', () => {
let defaultTaxWithholding: BigNumber;
let taxWithholdings: TaxWithholding[];

let corporateAction: CorporateAction;
let corporateAction: CorporateActionBase;

let corporateActionsQueryStub: sinon.SinonStub;

// eslint-disable-next-line require-jsdoc
class NonAbstract extends CorporateActionBase {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
modifyCheckpoint = {} as any;
}

beforeAll(() => {
dsMockUtils.initMocks();
entityMockUtils.initMocks();
Expand Down Expand Up @@ -90,7 +96,7 @@ describe('CorporateAction class', () => {
),
});

corporateAction = new CorporateAction(
corporateAction = new NonAbstract(
{
id,
ticker,
Expand Down Expand Up @@ -118,7 +124,7 @@ describe('CorporateAction class', () => {
});

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

describe('constructor', () => {
Expand All @@ -135,12 +141,12 @@ describe('CorporateAction class', () => {

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

Expand Down Expand Up @@ -288,24 +294,6 @@ describe('CorporateAction class', () => {
});
});

describe('method: modifyCaCheckpoint', () => {
test('should prepare the procedure and return the resulting transaction queue', async () => {
const expectedQueue = ('someQueue' as unknown) as TransactionQueue<void>;
const args = {
checkpoint: new Date(),
};

procedureMockUtils
.getPrepareStub()
.withArgs({ args: { corporateAction, ...args }, transformer: undefined }, context)
.resolves(expectedQueue);

const queue = await corporateAction.modifyCheckpoint(args);

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

describe('method: toJson', () => {
test('should return a human readable version of the entity', () => {
expect(corporateAction.toJson()).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
Entity,
linkCaDocs,
LinkCaDocsParams,
modifyCaCheckpoint,
ModifyCaCheckpointParams,
PolymeshError,
} from '~/internal';
Expand Down Expand Up @@ -53,7 +52,7 @@ export interface Params {
* Represents an action initiated by the issuer of a Security Token which may affect the positions of
* the Tokenholders
*/
export class CorporateAction extends Entity<UniqueIdentifiers, unknown> {
export abstract class CorporateActionBase extends Entity<UniqueIdentifiers, unknown> {
/**
* @hidden
* Check if a value is of type [[UniqueIdentifiers]]
Expand Down Expand Up @@ -85,7 +84,7 @@ export class CorporateAction extends Entity<UniqueIdentifiers, unknown> {
public description: string;

/**
* tokenholder identities related to this Corporate action. If the treatment is `Exclude`, the identities
* tokenholder identities related to this Corporate action. If the treatment is `Exclude`, the Identities
* are not targeted by the Action, and any identities left out of the array will be targeted, and vice versa
*/
public targets: CorporateActionTargets;
Expand Down Expand Up @@ -134,16 +133,6 @@ export class CorporateAction extends Entity<UniqueIdentifiers, unknown> {
{ getProcedureAndArgs: procedureArgs => [linkCaDocs, { id, ticker, ...procedureArgs }] },
context
);

this.modifyCheckpoint = createProcedureMethod(
{
getProcedureAndArgs: modifyCaCheckpointArgs => [
modifyCaCheckpoint,
{ corporateAction: this, ...modifyCaCheckpointArgs },
],
},
context
);
}

/**
Expand All @@ -156,7 +145,12 @@ export class CorporateAction extends Entity<UniqueIdentifiers, unknown> {
/**
* Modify the Corporate Action's Checkpoint
*/
public modifyCheckpoint: ProcedureMethod<ModifyCaCheckpointParams, void>;
public abstract modifyCheckpoint: ProcedureMethod<
Omit<ModifyCaCheckpointParams, 'checkpoint'> & {
checkpoint: Checkpoint | CheckpointSchedule | Date;
},
void
>;

/**
* Determine whether this Corporate Action exists on chain
Expand Down
File renamed without changes.
9 changes: 4 additions & 5 deletions src/api/entities/DividendDistribution/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import sinon from 'sinon';
import {
Checkpoint,
Context,
CorporateAction,
CorporateActionBase,
DefaultPortfolio,
DividendDistribution,
Entity,
TransactionQueue,
} from '~/internal';
import { getHistoryOfPaymentEventsForCa, getWithholdingTaxesOfCa } from '~/middleware/queries';
Expand Down Expand Up @@ -123,8 +122,8 @@ describe('DividendDistribution class', () => {
procedureMockUtils.cleanup();
});

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

describe('constructor', () => {
Expand All @@ -142,7 +141,7 @@ describe('DividendDistribution class', () => {
describe('method: checkpoint', () => {
test('should just pass the call down the line', async () => {
const fakeResult = ('checkpoint' as unknown) as Checkpoint;
sinon.stub(CorporateAction.prototype, 'checkpoint').resolves(fakeResult);
sinon.stub(CorporateActionBase.prototype, 'checkpoint').resolves(fakeResult);

const result = await dividendDistribution.checkpoint();

Expand Down
15 changes: 10 additions & 5 deletions src/api/entities/DividendDistribution/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import {
Params as CorporateActionParams,
UniqueIdentifiers,
} from '~/api/entities/CorporateAction';
import { CorporateActionBase } from '~/api/entities/CorporateActionBase';
import {
Checkpoint,
CheckpointSchedule,
claimDividends,
Context,
CorporateAction,
DefaultPortfolio,
Identity,
ModifyCaCheckpointParams,
modifyDistributionCheckpoint,
ModifyDistributionCheckpointParams,
NumberedPortfolio,
payDividends,
PayDividendsParams,
Expand Down Expand Up @@ -82,7 +82,7 @@ const notExistsMessage = 'The Dividend Distribution no longer exists';
* Represents a Corporate Action via which a Security Token issuer wishes to distribute dividends
* between a subset of the Tokenholders (targets)
*/
export class DividendDistribution extends CorporateAction {
export class DividendDistribution extends CorporateActionBase {
/**
* Portfolio from which the dividends will be distributed
*/
Expand Down Expand Up @@ -180,9 +180,14 @@ export class DividendDistribution extends CorporateAction {
public claim: ProcedureMethod<void, void>;

/**
* Modify the Distribution's checkpoint
* Modify the Distribution's Checkpoint
*/
public modifyCheckpoint: ProcedureMethod<ModifyDistributionCheckpointParams, void>;
public modifyCheckpoint: ProcedureMethod<
Omit<ModifyCaCheckpointParams, 'checkpoint'> & {
checkpoint: Checkpoint | CheckpointSchedule | Date;
},
void
>;

/**
* Transfer the corresponding share of the dividends to a list of Identities
Expand Down
109 changes: 109 additions & 0 deletions src/api/entities/__tests__/CorporateAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import BigNumber from 'bignumber.js';

import { Context, CorporateAction, CorporateActionBase, TransactionQueue } from '~/internal';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import {
CorporateActionKind,
CorporateActionTargets,
TargetTreatment,
TaxWithholding,
} from '~/types';

jest.mock(
'~/api/entities/Checkpoint',
require('~/testUtils/mocks/entities').mockCheckpointModule('~/api/entities/Checkpoint')
);
jest.mock(
'~/api/entities/CheckpointSchedule',
require('~/testUtils/mocks/entities').mockCheckpointScheduleModule(
'~/api/entities/CheckpointSchedule'
)
);
jest.mock(
'~/base/Procedure',
require('~/testUtils/mocks/procedure').mockProcedureModule('~/base/Procedure')
);

describe('CorporateAction class', () => {
let context: Context;
let id: BigNumber;
let ticker: string;
let declarationDate: Date;
let kind: CorporateActionKind;
let description: string;
let targets: CorporateActionTargets;
let defaultTaxWithholding: BigNumber;
let taxWithholdings: TaxWithholding[];

let corporateAction: CorporateAction;

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

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

id = new BigNumber(1);
ticker = 'SOME_TICKER';
declarationDate = new Date('10/14/1987 UTC');
kind = CorporateActionKind.UnpredictableBenefit;
description = 'someDescription';
targets = {
identities: [],
treatment: TargetTreatment.Exclude,
};
defaultTaxWithholding = new BigNumber(10);
taxWithholdings = [];

corporateAction = new CorporateAction(
{
id,
ticker,
kind,
declarationDate,
description,
targets,
defaultTaxWithholding,
taxWithholdings,
},
context
);
});

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

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

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

describe('method: modifyCheckpoint', () => {
test('should prepare the procedure and return the resulting transaction queue', async () => {
const expectedQueue = ('someQueue' as unknown) as TransactionQueue<void>;
const args = {
checkpoint: new Date(),
};

procedureMockUtils
.getPrepareStub()
.withArgs({ args: { corporateAction, ...args }, transformer: undefined }, context)
.resolves(expectedQueue);

const queue = await corporateAction.modifyCheckpoint(args);

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

0 comments on commit 845ebc2

Please sign in to comment.