Skip to content

Commit

Permalink
feat(instructions): accept value date as a parameter in addInstruction
Browse files Browse the repository at this point in the history
  • Loading branch information
monitz87 committed Jan 7, 2021
1 parent 4b8536e commit b98ffd8
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 49 deletions.
5 changes: 5 additions & 0 deletions src/api/entities/Instruction/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ describe('Instruction class', () => {
const status = InstructionStatus.Pending;
const createdAt = new Date('10/14/1987');
const tradeDate = new Date('11/17/1987');
const valueDate = new Date('11/17/1987');
const venueId = new BigNumber(1);
const venue = entityMockUtils.getVenueInstance({ id: venueId });
let type = InstructionType.SettleOnAffirmation;
Expand All @@ -98,6 +99,7 @@ describe('Instruction class', () => {
venue_id: dsMockUtils.createMockU64(venueId.toNumber()),
created_at: dsMockUtils.createMockOption(dsMockUtils.createMockMoment(createdAt.getTime())),
trade_date: dsMockUtils.createMockOption(dsMockUtils.createMockMoment(tradeDate.getTime())),
value_date: dsMockUtils.createMockOption(dsMockUtils.createMockMoment(valueDate.getTime())),
settlement_type: dsMockUtils.createMockSettlementType(type),
/* eslint-enable @typescript-eslint/camelcase */
};
Expand All @@ -113,6 +115,7 @@ describe('Instruction class', () => {
status,
createdAt,
tradeDate,
valueDate,
type,
venue,
});
Expand All @@ -124,6 +127,7 @@ describe('Instruction class', () => {
...queryResult,
/* eslint-disable @typescript-eslint/camelcase */
trade_date: dsMockUtils.createMockOption(),
value_date: dsMockUtils.createMockOption(),
settlement_type: dsMockUtils.createMockSettlementType({
SettleOnBlock: dsMockUtils.createMockU32(endBlock.toNumber()),
}),
Expand All @@ -136,6 +140,7 @@ describe('Instruction class', () => {
status,
createdAt,
tradeDate: null,
valueDate: null,
type,
endBlock,
venue,
Expand Down
2 changes: 2 additions & 0 deletions src/api/entities/Instruction/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export class Instruction extends Entity<UniqueIdentifiers> {
status,
created_at: createdAt,
trade_date: tradeDate,
value_date: valueDate,
settlement_type: type,
venue_id: venueId,
} = await settlement.instructionDetails(numberToU64(id, context));
Expand All @@ -107,6 +108,7 @@ export class Instruction extends Entity<UniqueIdentifiers> {
status: meshInstructionStatusToInstructionStatus(status),
createdAt: momentToDate(createdAt.unwrap()),
tradeDate: tradeDate.isSome ? momentToDate(tradeDate.unwrap()) : null,
valueDate: valueDate.isSome ? momentToDate(valueDate.unwrap()) : null,
venue: new Venue({ id: u64ToBigNumber(venueId) }, context),
};

Expand Down
7 changes: 5 additions & 2 deletions src/api/entities/Instruction/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ export type InstructionDetails = {
status: InstructionStatus;
createdAt: Date;
/**
* Date from which the Instruction can be executed. A null value means
* that it can be executed at any time
* Date at which the trade was agreed upon (optional, for offchain trades)
*/
tradeDate: Date | null;
/**
* Date at which the trade was executed (optional, for offchain trades)
*/
valueDate: Date | null;
venue: Venue;
} & (
| {
Expand Down
3 changes: 2 additions & 1 deletion src/api/entities/Venue/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ export class Venue extends Entity<UniqueIdentifiers> {
* Creates a settlement Instruction in this Venue
*
* @param args.legs - array of token movements (amount, from, to, token)
* @param args.tradeDate - date from which the Instruction is valid and can be authorized by the participants (optional, instruction will be valid from the start if not supplied)
* @param args.tradeDate - date at which the trade was agreed upon (optional, for offchain trades)
* @param args.valueDate - date at which the trade was executed (optional, for offchain trades)
* @param args.endBlock - block at which the Instruction will be executed automatically (optional, the Instruction will be executed when all participants have authorized it if not supplied)
*/

Expand Down
32 changes: 30 additions & 2 deletions src/api/procedures/__tests__/addInstruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('addInstruction procedure', () => {
let toPortfolio: DefaultPortfolio | NumberedPortfolio;
let token: string;
let tradeDate: Date;
let valueDate: Date;
let endBlock: BigNumber;
let args: Params;

Expand All @@ -69,6 +70,7 @@ describe('addInstruction procedure', () => {
let rawTo: PortfolioId;
let rawToken: Ticker;
let rawTradeDate: Moment;
let rawValueDate: Moment;
let rawEndBlock: u32;
let rawAuthSettlementType: SettlementType;
let rawBlockSettlementType: SettlementType;
Expand Down Expand Up @@ -115,7 +117,9 @@ describe('addInstruction procedure', () => {
id: new BigNumber(2),
});
token = 'SOME_TOKEN';
tradeDate = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
const now = new Date();
tradeDate = new Date(now.getTime() + 24 * 60 * 60 * 1000);
valueDate = new Date(now.getTime() + 24 * 60 * 60 * 1000 + 1);
endBlock = new BigNumber(1000);
rawVenueId = dsMockUtils.createMockU64(venueId.toNumber());
rawAmount = dsMockUtils.createMockBalance(amount.toNumber());
Expand All @@ -129,6 +133,7 @@ describe('addInstruction procedure', () => {
});
rawToken = dsMockUtils.createMockTicker(token);
rawTradeDate = dsMockUtils.createMockMoment(tradeDate.getTime());
rawValueDate = dsMockUtils.createMockMoment(valueDate.getTime());
rawEndBlock = dsMockUtils.createMockU32(endBlock.toNumber());
rawAuthSettlementType = dsMockUtils.createMockSettlementType('SettleOnAffirmation');
rawBlockSettlementType = dsMockUtils.createMockSettlementType({ SettleOnBlock: rawEndBlock });
Expand Down Expand Up @@ -210,6 +215,7 @@ describe('addInstruction procedure', () => {
.withArgs({ type: InstructionType.SettleOnAffirmation }, mockContext)
.returns(rawAuthSettlementType);
dateToMomentStub.withArgs(tradeDate, mockContext).returns(rawTradeDate);
dateToMomentStub.withArgs(valueDate, mockContext).returns(rawValueDate);
});

afterEach(() => {
Expand Down Expand Up @@ -258,6 +264,27 @@ describe('addInstruction procedure', () => {
expect(error.message).toBe('End block must be a future block');
});

test('should throw an error if the value date is before the trade date', async () => {
dsMockUtils.configureMocks({ contextOptions: { latestBlock: new BigNumber(1000) } });
const proc = procedureMockUtils.getInstance<Params, Instruction, Storage>(mockContext, {
portfoliosToAffirm: [],
});

let error;

try {
await prepareAddInstruction.call(proc, {
...args,
tradeDate: new Date(valueDate.getTime() + 1),
valueDate,
});
} catch (err) {
error = err;
}

expect(error.message).toBe('Value date must be after trade date');
});

test('should add an add and authorize instruction transaction to the queue', async () => {
dsMockUtils.configureMocks({ contextOptions: { did: fromDid } });
getCustodianStub.onCall(1).returns({ did: fromDid });
Expand Down Expand Up @@ -296,6 +323,7 @@ describe('addInstruction procedure', () => {
{ from, to, amount, token: entityMockUtils.getSecurityTokenInstance({ ticker: token }) },
],
tradeDate,
valueDate,
endBlock,
});

Expand All @@ -308,7 +336,7 @@ describe('addInstruction procedure', () => {
rawVenueId,
rawBlockSettlementType,
rawTradeDate,
null,
rawValueDate,
[rawLeg]
);
expect(result).toBe(instruction);
Expand Down
26 changes: 0 additions & 26 deletions src/api/procedures/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,32 +162,6 @@ describe('assertInstructionValid', () => {
);
});

test('should throw an error if instruction is blocked', async () => {
const tradeDate = new Date('12/12/2050');

entityMockUtils.configureMocks({
instructionOptions: {
details: {
status: InstructionStatus.Pending,
tradeDate,
} as InstructionDetails,
},
});

instruction = getInstructionInstance();

let error;

try {
await assertInstructionValid(instruction, mockContext);
} catch (err) {
error = err;
}

expect(error.message).toBe('The instruction has not reached its validity period');
expect(error.data.tradeDate).toEqual(tradeDate);
});

test('should throw an error if the instruction can not be modified', async () => {
const endBlock = new BigNumber(10);

Expand Down
15 changes: 12 additions & 3 deletions src/api/procedures/addInstruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface AddInstructionParams {
token: string | SecurityToken;
}[];
tradeDate?: Date;
valueDate?: Date;
endBlock?: BigNumber;
}

Expand Down Expand Up @@ -80,7 +81,7 @@ export async function prepareAddInstruction(
context,
storage: { portfoliosToAffirm },
} = this;
const { legs, venueId, endBlock, tradeDate } = args;
const { legs, venueId, endBlock, tradeDate, valueDate } = args;

if (!legs.length) {
throw new PolymeshError({
Expand All @@ -106,9 +107,17 @@ export async function prepareAddInstruction(
endCondition = { type: InstructionType.SettleOnAffirmation } as const;
}

if (tradeDate && valueDate && tradeDate > valueDate) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'Value date must be after trade date',
});
}

const rawVenueId = numberToU64(venueId, context);
const rawSettlementType = endConditionToSettlementType(endCondition, context);
const rawTradeDate = tradeDate ? dateToMoment(tradeDate, context) : null;
const rawValueDate = valueDate ? dateToMoment(valueDate, context) : null;
const rawLegs: {
from: PortfolioId;
to: PortfolioId;
Expand Down Expand Up @@ -149,7 +158,7 @@ export async function prepareAddInstruction(
rawVenueId,
rawSettlementType,
rawTradeDate,
null,
rawValueDate,
rawLegs,
portfoliosToAffirm.map(portfolio =>
portfolioIdToMeshPortfolioId(portfolioLikeToPortfolioId(portfolio), context)
Expand All @@ -164,7 +173,7 @@ export async function prepareAddInstruction(
rawVenueId,
rawSettlementType,
rawTradeDate,
null,
rawValueDate,
rawLegs
);
}
Expand Down
16 changes: 1 addition & 15 deletions src/api/procedures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function assertInstructionValid(
context: Context
): Promise<void> {
const details = await instruction.details();
const { status, tradeDate } = await instruction.details();
const { status } = await instruction.details();

if (status !== InstructionStatus.Pending) {
throw new PolymeshError({
Expand All @@ -52,20 +52,6 @@ export async function assertInstructionValid(
});
}

if (tradeDate) {
const now = new Date();

if (now < tradeDate) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'The instruction has not reached its validity period',
data: {
tradeDate,
},
});
}
}

if (details.type === InstructionType.SettleOnBlock) {
const latestBlock = await context.getLatestBlock();
const { endBlock } = details;
Expand Down
1 change: 1 addition & 0 deletions src/testUtils/mocks/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ const defaultInstructionOptions: InstructionOptions = {
status: InstructionStatus.Pending,
createdAt: new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000),
tradeDate: null,
valueDate: null,
type: InstructionType.SettleOnAffirmation,
},
};
Expand Down

0 comments on commit b98ffd8

Please sign in to comment.