Skip to content

Commit

Permalink
chore: msdk-313 refactor transfers namespace
Browse files Browse the repository at this point in the history
The following breaking changes are from earlier commits

BREAKING CHANGE:
- `SigningKey` interface renamed to `SecondaryKey`:
- `getSigningKeys()` and `removeSigningKeys()` in `CurrentIdentity` renamed to `getSecondaryKeys()` and `removeSecondaryKeys()`
- `api.registerIdentity()` arguments changed from `{ targetAccount: string | Account; expiry?: Date; signingKeys?: SigningKey[] }` to `{ targetAccount: string | Account; secondaryKeys?: SecondaryKey[] }`
- `getMasterKey()` in `Identity` renamed to `getPrimaryKey`
- `AttestMasterKeyRotation` and `RotateMasterKey` in the `AuthorizationType` enum renamed to `AttestPrimaryKeyRotation` and `RotatePrimaryKey` respectively
- `Rule` interface renamed to `Requirement`
- `RuleCompliance` interface renamed to `RequirementCompliance` (`rules` key changed to `requirements`)
- `rules` namespace in `securityToken.compliance` renamed to `requirements`
- `requirements.set()` arguments changed from `{ rules: Condition[][] }` to `{ requirements: Condition[][] }`
- `checkMint()` removed from `securityToken.compliance.requirements`
- `canMint()` and `transfer()` removed from `securityToken.transfers`
- `treasuryIdentity` in `SecurityTokenDetails` renamed to `primaryIssuanceAgent`
- `api.governance` namespace deleted, and all governance/proposal related classes/functionality disabled
- `securityToken.issuance.issue()` arguments changed from `{ issuanceData: IssuanceData }` to `{ amount: BigNumber }`
- `tickerReservation.createSecurityToken()` arguments no longer accept a `treasury` parameter. The treasury DID is now always the issuer by default
- Jurisdiction claim interface changed to `{ type: ClaimType.Jurisdiction; code: CountryCode; scope: Scope }`
  • Loading branch information
shuffledex committed Sep 29, 2020
1 parent c2797f6 commit ec725b1
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 164 deletions.
Expand Up @@ -2,11 +2,8 @@ import BigNumber from 'bignumber.js';
import { CanTransferResult } from 'polymesh-types/types';

import { Identity, Namespace, SecurityToken } from '~/api/entities';
import { toggleFreezeTransfers } from '~/api/procedures';
import { TransactionQueue } from '~/base';
import { SubCallback, TransferStatus, UnsubCallback } from '~/types';
import { TransferStatus } from '~/types';
import {
boolToBoolean,
canTransferResultToTransferStatus,
numberToBalance,
portfolioIdToMeshPortfolioId,
Expand All @@ -17,72 +14,17 @@ import {
import { DUMMY_ACCOUNT_ID } from '~/utils/constants';

/**
* Handles all Security Token Transfer related functionality
* Handles all Security Token Settlements related functionality
*/
export class Transfers extends Namespace<SecurityToken> {
/**
* Freezes transfers and minting of the Security Token
*/
public freeze(): Promise<TransactionQueue<SecurityToken>> {
const {
parent: { ticker },
context,
} = this;
return toggleFreezeTransfers.prepare({ ticker, freeze: true }, context);
}

/**
* Unfreeze transfers and minting of the Security Token
*/
public unfreeze(): Promise<TransactionQueue<SecurityToken>> {
const {
parent: { ticker },
context,
} = this;
return toggleFreezeTransfers.prepare({ ticker, freeze: false }, context);
}

/**
* Check whether transfers are frozen for the Security Token
*
* @note can be subscribed to
*/
public areFrozen(): Promise<boolean>;
public areFrozen(callback: SubCallback<boolean>): Promise<UnsubCallback>;

// eslint-disable-next-line require-jsdoc
public async areFrozen(callback?: SubCallback<boolean>): Promise<boolean | UnsubCallback> {
const {
parent: { ticker },
context: {
polymeshApi: {
query: { asset },
},
},
context,
} = this;

const rawTicker = stringToTicker(ticker, context);

if (callback) {
return asset.frozen(rawTicker, frozen => {
callback(boolToBoolean(frozen));
});
}

const result = await asset.frozen(rawTicker);

return boolToBoolean(result);
}

export class Settlements extends Namespace<SecurityToken> {
/**
* Check whether it is possible to transfer a certain amount of this asset between two Identities
*
* @param args.from - sender Identity (optional, defaults to the current Identity)
* @param args.to - receiver Identity
* @param args.amount - amount of tokens to transfer
*/
public async canTransfer(args: {
public async canSettle(args: {
from?: string | Identity;
to: string | Identity;
amount: BigNumber;
Expand Down
@@ -1,31 +1,24 @@
import { AccountId, Balance } from '@polkadot/types/interfaces';
import { bool } from '@polkadot/types/primitive';
import BigNumber from 'bignumber.js';
import { PortfolioId as MeshPortfolioId, Ticker } from 'polymesh-types/types';
import sinon, { SinonStub } from 'sinon';

import { Namespace } from '~/api/entities';
import { toggleFreezeTransfers } from '~/api/procedures';
import { Params } from '~/api/procedures/toggleFreezeTransfers';
import { Context, TransactionQueue } from '~/base';
import { Context } from '~/base';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { TransferStatus } from '~/types';
import { PortfolioId } from '~/types/internal';
import * as utilsModule from '~/utils';
import { DUMMY_ACCOUNT_ID } from '~/utils/constants';

import { SecurityToken } from '../';
import { Transfers } from '../Transfers';
import { SecurityToken } from '..';
import { Settlements } from '../Settlements';

describe('Transfers class', () => {
describe('Settlements class', () => {
let mockContext: Mocked<Context>;
let mockSecurityToken: Mocked<SecurityToken>;
let transfers: Transfers;
let prepareToggleFreezeTransfersStub: SinonStub<
[Params, Context],
Promise<TransactionQueue<SecurityToken, unknown[][]>>
>;
let settlements: Settlements;
let stringToAccountIdStub: SinonStub<[string, Context], AccountId>;
let stringToTickerStub: SinonStub<[string, Context], Ticker>;
let numberToBalanceStub: SinonStub<[number | BigNumber, Context], Balance>;
Expand All @@ -50,14 +43,13 @@ describe('Transfers class', () => {
numberToBalanceStub = sinon.stub(utilsModule, 'numberToBalance');
portfolioIdToMeshPortfolioIdStub = sinon.stub(utilsModule, 'portfolioIdToMeshPortfolioId');
rawAmount = dsMockUtils.createMockBalance(amount.toNumber());
prepareToggleFreezeTransfersStub = sinon.stub(toggleFreezeTransfers, 'prepare');
});

beforeEach(() => {
mockContext = dsMockUtils.getContextInstance();
mockSecurityToken = entityMockUtils.getSecurityTokenInstance();
numberToBalanceStub.withArgs(amount, mockContext).returns(rawAmount);
transfers = new Transfers(mockSecurityToken, mockContext);
settlements = new Settlements(mockSecurityToken, mockContext);
ticker = mockSecurityToken.ticker;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
accountId = mockContext.currentPair?.address!;
Expand All @@ -76,76 +68,10 @@ describe('Transfers class', () => {
});

test('should extend namespace', () => {
expect(Transfers.prototype instanceof Namespace).toBe(true);
expect(Settlements.prototype instanceof Namespace).toBe(true);
});

describe('method: freeze', () => {
test('should prepare the procedure and return the resulting transaction queue', async () => {
const expectedQueue = ('someQueue' as unknown) as TransactionQueue<SecurityToken>;

prepareToggleFreezeTransfersStub
.withArgs({ ticker: mockSecurityToken.ticker, freeze: true }, mockContext)
.resolves(expectedQueue);

const queue = await transfers.freeze();

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

describe('method: unfreeze', () => {
test('should prepare the procedure and return the resulting transaction queue', async () => {
const expectedQueue = ('someQueue' as unknown) as TransactionQueue<SecurityToken>;

prepareToggleFreezeTransfersStub
.withArgs({ ticker: mockSecurityToken.ticker, freeze: false }, mockContext)
.resolves(expectedQueue);

const queue = await transfers.unfreeze();

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

describe('method: areFrozen', () => {
let frozenStub: sinon.SinonStub;
let boolValue: boolean;
let rawBoolValue: bool;

beforeAll(() => {
boolValue = true;
rawBoolValue = dsMockUtils.createMockBool(boolValue);
});

beforeEach(() => {
frozenStub = dsMockUtils.createQueryStub('asset', 'frozen');
});

test('should return whether the security token is frozen or not', async () => {
frozenStub.resolves(rawBoolValue);

const result = await transfers.areFrozen();

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

test('should allow subscription', async () => {
const unsubCallback = 'unsubCallBack';

frozenStub.callsFake(async (_, cbFunc) => {
cbFunc(rawBoolValue);
return unsubCallback;
});

const callback = sinon.stub();
const result = await transfers.areFrozen(callback);

expect(result).toBe(unsubCallback);
sinon.assert.calledWithExactly(callback, boolValue);
});
});

describe('method: canTransfer', () => {
describe('method: canSettle', () => {
let fromDid: string;
const rawFromPortfolio = dsMockUtils.createMockPortfolioId();
const rawToPortfolio = dsMockUtils.createMockPortfolioId();
Expand Down Expand Up @@ -192,7 +118,7 @@ describe('Transfers class', () => {
)
.returns(rawResponse);

const result = await transfers.canTransfer({ to: toDid, amount });
const result = await settlements.canSettle({ to: toDid, amount });

expect(result).toBe(TransferStatus.Success);
});
Expand All @@ -211,7 +137,7 @@ describe('Transfers class', () => {
.withArgs(rawAccountId, null, rawFromPortfolio, null, rawToPortfolio, rawTicker, rawAmount)
.returns(rawResponse);

const result = await transfers.canTransfer({ from: fromDid, to: toDid, amount });
const result = await settlements.canSettle({ from: fromDid, to: toDid, amount });

expect(result).toBe(TransferStatus.Success);
});
Expand Down
91 changes: 90 additions & 1 deletion src/api/entities/SecurityToken/__tests__/index.ts
@@ -1,14 +1,16 @@
import { Balance } from '@polkadot/types/interfaces';
import { bool } from '@polkadot/types/primitive';
import BigNumber from 'bignumber.js';
import {
AssetIdentifier,
FundingRoundName,
SecurityToken as MeshSecurityToken,
} from 'polymesh-types/types';
import sinon from 'sinon';
import sinon, { SinonStub } from 'sinon';

import { Entity, Identity } from '~/api/entities';
import { modifyToken, transferTokenOwnership } from '~/api/procedures';
import { Params, toggleFreezeTransfers } from '~/api/procedures/toggleFreezeTransfers';
import { Context, TransactionQueue } from '~/base';
import { eventByIndexedArgs } from '~/middleware/queries';
import { EventIdEnum, ModuleIdEnum } from '~/middleware/types';
Expand All @@ -20,8 +22,14 @@ import { MAX_TICKER_LENGTH } from '~/utils/constants';
import { SecurityToken } from '../';

describe('SecurityToken class', () => {
let prepareToggleFreezeTransfersStub: SinonStub<
[Params, Context],
Promise<TransactionQueue<SecurityToken, unknown[][]>>
>;

beforeAll(() => {
dsMockUtils.initMocks();
prepareToggleFreezeTransfersStub = sinon.stub(toggleFreezeTransfers, 'prepare');
});

afterEach(() => {
Expand Down Expand Up @@ -365,4 +373,85 @@ describe('SecurityToken class', () => {
expect(result).toBeNull();
});
});

describe('method: freeze', () => {
test('should prepare the procedure and return the resulting transaction queue', async () => {
const ticker = 'TICKER';
const context = dsMockUtils.getContextInstance();
const securityToken = new SecurityToken({ ticker }, context);

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

prepareToggleFreezeTransfersStub
.withArgs({ ticker, freeze: true }, context)
.resolves(expectedQueue);

const queue = await securityToken.freeze();

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

describe('method: unfreeze', () => {
test('should prepare the procedure and return the resulting transaction queue', async () => {
const ticker = 'TICKER';
const context = dsMockUtils.getContextInstance();
const securityToken = new SecurityToken({ ticker }, context);

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

prepareToggleFreezeTransfersStub
.withArgs({ ticker, freeze: false }, context)
.resolves(expectedQueue);

const queue = await securityToken.unfreeze();

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

describe('method: isFrozen', () => {
let frozenStub: sinon.SinonStub;
let boolValue: boolean;
let rawBoolValue: bool;

beforeAll(() => {
boolValue = true;
rawBoolValue = dsMockUtils.createMockBool(boolValue);
});

beforeEach(() => {
frozenStub = dsMockUtils.createQueryStub('asset', 'frozen');
});

test('should return whether the security token is frozen or not', async () => {
const ticker = 'TICKER';
const context = dsMockUtils.getContextInstance();
const securityToken = new SecurityToken({ ticker }, context);

frozenStub.resolves(rawBoolValue);

const result = await securityToken.isFrozen();

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

test('should allow subscription', async () => {
const ticker = 'TICKER';
const context = dsMockUtils.getContextInstance();
const securityToken = new SecurityToken({ ticker }, context);
const unsubCallback = 'unsubCallBack';

frozenStub.callsFake(async (_, cbFunc) => {
cbFunc(rawBoolValue);
return unsubCallback;
});

const callback = sinon.stub();
const result = await securityToken.isFrozen(callback);

expect(result).toBe(unsubCallback);
sinon.assert.calledWithExactly(callback, boolValue);
});
});
});

0 comments on commit ec725b1

Please sign in to comment.