Skip to content

Commit

Permalink
feat(checkpoints): add checkpoints.create to the Security Token Entity
Browse files Browse the repository at this point in the history
Also upgraded the SDK to use Polymesh 2.5
  • Loading branch information
monitz87 committed Feb 16, 2021
1 parent 73f945c commit 28a2b82
Show file tree
Hide file tree
Showing 20 changed files with 631 additions and 420 deletions.
3 changes: 2 additions & 1 deletion scripts/transactions.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@
"affirm_instruction": "affirm_instruction",
"withdraw_affirmation": "withdraw_affirmation",
"affirm_with_receipts": "affirm_with_receipts",
"execute_scheduled_instruction": "execute_scheduled_instruction"
"execute_scheduled_instruction": "execute_scheduled_instruction",
"change_receipt_validity": "change_receipt_validity"
},
"Sto": {
"create_fundraiser": "create_fundraiser",
Expand Down
27 changes: 27 additions & 0 deletions src/api/entities/SecurityToken/Checkpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Context, createCheckpoint, Namespace, SecurityToken } from '~/internal';
import { ProcedureMethod } from '~/types/internal';
import { createProcedureMethod } from '~/utils/internal';

/**
* Handles all Security Token Checkpoints related functionality
*/
export class Checkpoints extends Namespace<SecurityToken> {
/**
* @hidden
*/
constructor(parent: SecurityToken, context: Context) {
super(parent, context);

const { ticker } = parent;

this.create = createProcedureMethod(() => [createCheckpoint, { ticker }], context);
}

/**
* Create a snapshot of Security Token holders and their respective balances at this moment
*
* @note required role:
* - Security Token Owner
*/
public create: ProcedureMethod<void, SecurityToken>;
}
87 changes: 87 additions & 0 deletions src/api/procedures/__tests__/createCheckpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Ticker, TxTags } from 'polymesh-types/types';
import sinon from 'sinon';

import {
getAuthorization,
Params,
prepareCreateCheckpoint,
} from '~/api/procedures/createCheckpoint';
import { Context, SecurityToken } from '~/internal';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { RoleType } from '~/types';
import * as utilsConversionModule from '~/utils/conversion';

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

describe('createCheckpoint procedure', () => {
let mockContext: Mocked<Context>;
let stringToTickerStub: sinon.SinonStub<[string, Context], Ticker>;
let ticker: string;
let rawTicker: Ticker;

beforeAll(() => {
dsMockUtils.initMocks();
procedureMockUtils.initMocks();
entityMockUtils.initMocks();
stringToTickerStub = sinon.stub(utilsConversionModule, 'stringToTicker');
ticker = 'SOME_TICKER';
rawTicker = dsMockUtils.createMockTicker(ticker);
});

let addTransactionStub: sinon.SinonStub;

beforeEach(() => {
addTransactionStub = procedureMockUtils.getAddTransactionStub();
mockContext = dsMockUtils.getContextInstance();
stringToTickerStub.withArgs(ticker, mockContext).returns(rawTicker);
});

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

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

test('should add a create checkpoint transaction to the queue', async () => {
const proc = procedureMockUtils.getInstance<Params, SecurityToken>(mockContext);

const transaction = dsMockUtils.createTxStub('checkpoint', 'createCheckpoint');

const result = await prepareCreateCheckpoint.call(proc, {
ticker,
});

sinon.assert.calledWith(addTransactionStub, transaction, {}, rawTicker);

expect(ticker).toBe(result.ticker);
});

describe('getAuthorization', () => {
test('should return the appropriate roles and permissions', () => {
const proc = procedureMockUtils.getInstance<Params, SecurityToken>(mockContext);
const boundFunc = getAuthorization.bind(proc);

const token = entityMockUtils.getSecurityTokenInstance({ ticker });
const identityRoles = [{ type: RoleType.TokenOwner, ticker }];

expect(boundFunc({ ticker })).toEqual({
identityRoles,
signerPermissions: {
transactions: [TxTags.checkpoint.CreateCheckpoint],
tokens: [token],
portfolios: [],
},
});
});
});
});
85 changes: 46 additions & 39 deletions src/api/procedures/__tests__/modifyInstructionAffirmation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { u64 } from '@polkadot/types';
import { u32, u64 } from '@polkadot/types';
import BigNumber from 'bignumber.js';
import {
AffirmationStatus as MeshAffirmationStatus,
Expand Down Expand Up @@ -40,11 +40,15 @@ describe('modifyInstructionAffirmation procedure', () => {
did: dsMockUtils.createMockIdentityId('someDid'),
kind: dsMockUtils.createMockPortfolioKind('Default'),
});
const did = 'someDid';
let portfolio: DefaultPortfolio;
const portfolioId: PortfolioId = { did: 'someDid' };
let legAmount: number;
let rawLegAmount: u32;
const portfolioId: PortfolioId = { did };
const latestBlock = new BigNumber(100);
let mockContext: Mocked<Context>;
let numberToU64Stub: sinon.SinonStub<[number | BigNumber, Context], u64>;
let numberToU32Stub: sinon.SinonStub<[number | BigNumber, Context], u32>;
let portfolioLikeToPortfolioIdStub: sinon.SinonStub<[PortfolioLike], PortfolioId>;
let portfolioIdToMeshPortfolioIdStub: sinon.SinonStub<[PortfolioId, Context], MeshPortfolioId>;
let meshAffirmationStatusToAffirmationStatusStub: sinon.SinonStub<
Expand All @@ -62,7 +66,9 @@ describe('modifyInstructionAffirmation procedure', () => {
entityMockUtils.initMocks();

portfolio = entityMockUtils.getDefaultPortfolioInstance({ did: 'someDid ' });
legAmount = 2;
numberToU64Stub = sinon.stub(utilsConversionModule, 'numberToU64');
numberToU32Stub = sinon.stub(utilsConversionModule, 'numberToU32');
portfolioLikeToPortfolioIdStub = sinon.stub(
utilsConversionModule,
'portfolioLikeToPortfolioId'
Expand All @@ -82,12 +88,14 @@ describe('modifyInstructionAffirmation procedure', () => {
let addTransactionStub: sinon.SinonStub;

beforeEach(() => {
rawLegAmount = dsMockUtils.createMockU32(2);
dsMockUtils.createTxStub('settlement', 'affirmInstruction');
dsMockUtils.createTxStub('settlement', 'withdrawAffirmation');
dsMockUtils.createTxStub('settlement', 'rejectInstruction');
addTransactionStub = procedureMockUtils.getAddTransactionStub();
mockContext = dsMockUtils.getContextInstance();
numberToU64Stub.returns(rawInstructionId);
numberToU32Stub.returns(rawLegAmount);
portfolioLikeToPortfolioIdStub.withArgs(portfolio).returns(portfolioId);
portfolioIdToMeshPortfolioIdStub.withArgs(portfolioId, mockContext).returns(rawPortfolioId);
});
Expand Down Expand Up @@ -119,6 +127,7 @@ describe('modifyInstructionAffirmation procedure', () => {
Storage
>(mockContext, {
portfolios: [portfolio, portfolio],
senderLegAmount: legAmount,
});

return expect(
Expand All @@ -142,7 +151,7 @@ describe('modifyInstructionAffirmation procedure', () => {
ModifyInstructionAffirmationParams,
Instruction,
Storage
>(mockContext, { portfolios: [portfolio, portfolio] });
>(mockContext, { portfolios: [portfolio, portfolio], senderLegAmount: legAmount });

const transaction = dsMockUtils.createTxStub('settlement', 'affirmInstruction');

Expand All @@ -151,10 +160,14 @@ describe('modifyInstructionAffirmation procedure', () => {
operation: InstructionAffirmationOperation.Affirm,
});

sinon.assert.calledWith(addTransactionStub, transaction, {}, rawInstructionId, [
rawPortfolioId,
rawPortfolioId,
]);
sinon.assert.calledWith(
addTransactionStub,
transaction,
{ batchSize: 2 },
rawInstructionId,
[rawPortfolioId, rawPortfolioId],
rawLegAmount
);

expect(result.id).toEqual(id);
});
Expand All @@ -174,6 +187,7 @@ describe('modifyInstructionAffirmation procedure', () => {
Storage
>(mockContext, {
portfolios: [portfolio, portfolio],
senderLegAmount: legAmount,
});

return expect(
Expand All @@ -199,6 +213,7 @@ describe('modifyInstructionAffirmation procedure', () => {
Storage
>(mockContext, {
portfolios: [portfolio, portfolio],
senderLegAmount: legAmount,
});

return expect(
Expand All @@ -224,6 +239,7 @@ describe('modifyInstructionAffirmation procedure', () => {
Storage
>(mockContext, {
portfolios: [portfolio, portfolio],
senderLegAmount: legAmount,
});

const transaction = dsMockUtils.createTxStub('settlement', 'withdrawAffirmation');
Expand All @@ -233,10 +249,14 @@ describe('modifyInstructionAffirmation procedure', () => {
operation: InstructionAffirmationOperation.Withdraw,
});

sinon.assert.calledWith(addTransactionStub, transaction, {}, rawInstructionId, [
rawPortfolioId,
rawPortfolioId,
]);
sinon.assert.calledWith(
addTransactionStub,
transaction,
{ batchSize: 2 },
rawInstructionId,
[rawPortfolioId, rawPortfolioId],
rawLegAmount
);

expect(result.id).toEqual(id);
});
Expand All @@ -256,6 +276,7 @@ describe('modifyInstructionAffirmation procedure', () => {
Storage
>(mockContext, {
portfolios: [portfolio, portfolio],
senderLegAmount: legAmount,
});

return expect(
Expand All @@ -266,30 +287,7 @@ describe('modifyInstructionAffirmation procedure', () => {
).rejects.toThrow('The Instruction cannot be rejected');
});

test('should add an reject instruction transaction to the queue', async () => {
const currentDid = (await mockContext.getCurrentIdentity()).did;

entityMockUtils.configureMocks({
instructionOptions: {
getLegs: {
data: [
{
from: new DefaultPortfolio({ did: 'notTheCurrentIdentity' }, mockContext),
to: new DefaultPortfolio({ did: currentDid }, mockContext),
token: entityMockUtils.getSecurityTokenInstance(),
amount: new BigNumber(100),
},
{
from: new DefaultPortfolio({ did: currentDid }, mockContext),
to: new DefaultPortfolio({ did: 'notTheCurrentIdentity' }, mockContext),
token: entityMockUtils.getSecurityTokenInstance(),
amount: new BigNumber(200),
},
],
next: null,
},
},
});
test('should add a reject instruction transaction to the queue', async () => {
const rawAffirmationStatus = dsMockUtils.createMockAffirmationStatus('Pending');
dsMockUtils.createQueryStub('settlement', 'userAffirmations', {
multi: [rawAffirmationStatus, rawAffirmationStatus],
Expand All @@ -310,6 +308,7 @@ describe('modifyInstructionAffirmation procedure', () => {
Storage
>(mockContext, {
portfolios: [portfolio, portfolio],
senderLegAmount: legAmount,
});

const transaction = dsMockUtils.createTxStub('settlement', 'rejectInstruction');
Expand All @@ -319,10 +318,14 @@ describe('modifyInstructionAffirmation procedure', () => {
operation: InstructionAffirmationOperation.Reject,
});

sinon.assert.calledWith(addTransactionStub, transaction, {}, rawInstructionId, [
rawPortfolioId,
rawPortfolioId,
]);
sinon.assert.calledWith(
addTransactionStub,
transaction,
{ batchSize: 2 },
rawInstructionId,
[rawPortfolioId, rawPortfolioId],
rawLegAmount
);

expect(result.id).toEqual(id);
});
Expand All @@ -342,6 +345,7 @@ describe('modifyInstructionAffirmation procedure', () => {
Storage
>(mockContext, {
portfolios: [from, to],
senderLegAmount: legAmount,
});
let boundFunc = getAuthorization.bind(proc);

Expand All @@ -361,6 +365,7 @@ describe('modifyInstructionAffirmation procedure', () => {
Storage
>(mockContext, {
portfolios: [],
senderLegAmount: legAmount,
});

boundFunc = getAuthorization.bind(proc);
Expand Down Expand Up @@ -414,6 +419,7 @@ describe('modifyInstructionAffirmation procedure', () => {

expect(result).toEqual({
portfolios: [from, to],
senderLegAmount: 1,
});

from = entityMockUtils.getNumberedPortfolioInstance({ isCustodiedBy: false });
Expand All @@ -430,6 +436,7 @@ describe('modifyInstructionAffirmation procedure', () => {

expect(result).toEqual({
portfolios: [],
senderLegAmount: 0,
});
});
});
Expand Down

0 comments on commit 28a2b82

Please sign in to comment.