Skip to content

Commit

Permalink
Merge pull request #37 from PolymathNetwork/feat/MSDK-55-st-modify-ma…
Browse files Browse the repository at this point in the history
…ke-divisible

Feat/msdk 55 st modify make divisible
  • Loading branch information
monitz87 authored Mar 17, 2020
2 parents f958757 + 9066ae4 commit 5d87d8b
Show file tree
Hide file tree
Showing 16 changed files with 301 additions and 74 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ npm-package/
coverage/
test.ts
sandbox.ts
docs/
docs/
tsconfig.dev.json
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"author": "Polymath Studios Inc.",
"license": "ISC",
"scripts": {
"start": "cross-env webpack-dev-server --config=webpack.config.dev.js",
"start": "node scripts/generateTsconfigDev.js && cross-env webpack-dev-server --config=webpack.config.dev.js",
"generate:polkadot-types": "yarn fetch-schema && yarn generate:defs && yarn generate:meta && yarn generate:tags",
"fetch-schema": "node scripts/fetchSchema.js",
"generate:defs": "ts-node --skip-project node_modules/.bin/polkadot-types-from-defs --package polymesh-types --input ./src/polkadot",
Expand Down Expand Up @@ -66,7 +66,6 @@
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2",
"websocket": "^1.0.31"
},
"config": {
Expand Down
20 changes: 20 additions & 0 deletions scripts/generateTsconfigDev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* eslint-disable */
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');

const configFile = path.resolve(__dirname, '../tsconfig.json');
const configDevFile = path.resolve(__dirname, '../tsconfig.dev.json');

rimraf.sync(configDevFile);

let rawdata = fs.readFileSync(configFile);
let tsConfigJson = JSON.parse(rawdata);

tsConfigJson.compilerOptions.rootDir = '.';

let tsConfigDev = JSON.stringify({
compilerOptions: tsConfigJson.compilerOptions,
});

fs.writeFileSync(configDevFile, tsConfigDev);
28 changes: 27 additions & 1 deletion src/api/entities/SecurityToken/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Balance } from '@polkadot/types/interfaces';
import sinon from 'sinon';

import { Entity } from '~/base';
import { modifyToken } from '~/api/procedures';
import { Entity, TransactionQueue } from '~/base';
import { polkadotMockUtils } from '~/testUtils/mocks';
import { balanceToBigNumber } from '~/utils';

Expand Down Expand Up @@ -72,4 +74,28 @@ describe('SecurityToken class', () => {
expect(details.owner.did).toBe(owner);
});
});

describe('method: modify', () => {
test('should prepare the procedure with the correct arguments and context, and return the resulting transaction queue', async () => {
const ticker = 'TEST';
const context = polkadotMockUtils.getContextInstance();
const securityToken = new SecurityToken({ ticker }, context);
const makeDivisible: true = true;

const args = {
makeDivisible,
};

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

sinon
.stub(modifyToken, 'prepare')
.withArgs({ ticker, ...args }, context)
.resolves(expectedQueue);

const queue = await securityToken.modify(args);

expect(queue).toBe(expectedQueue);
});
});
});
14 changes: 13 additions & 1 deletion src/api/entities/SecurityToken/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Identity } from '~/api/entities/Identity';
import { Entity } from '~/base';
import { modifyToken, ModifyTokenParams } from '~/api/procedures';
import { Entity, TransactionQueue } from '~/base';
import { Context } from '~/context';
import { balanceToBigNumber, boolToBoolean, identityIdToString, tokenNameToString } from '~/utils';

Expand Down Expand Up @@ -45,6 +46,17 @@ export class SecurityToken extends Entity<UniqueIdentifiers> {
this.ticker = ticker;
}

/**
* Modify some properties of the Security Token
*
* @param args.makeDivisible - makes an indivisible token divisible
* @throws if the passed values result in no changes being made to the token
*/
public modify(args: ModifyTokenParams): Promise<TransactionQueue<SecurityToken>> {
const { ticker } = this;
return modifyToken.prepare({ ticker, ...args }, this.context);
}

/**
* Retrieve the Security Token's name, total supply, whether it is divisible or not and the identity of the owner
*/
Expand Down
123 changes: 123 additions & 0 deletions src/api/procedures/__tests__/modifyToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Ticker } from 'polymesh-types/types';
import sinon from 'sinon';

import { SecurityToken } from '~/api/entities';
import { Params, prepareModifyToken } from '~/api/procedures/modifyToken';
import { Context } from '~/context';
import { entityMockUtils, polkadotMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import * as utilsModule from '~/utils';

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

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

beforeAll(() => {
polkadotMockUtils.initMocks();
procedureMockUtils.initMocks();
entityMockUtils.initMocks();
stringToTickerStub = sinon.stub(utilsModule, 'stringToTicker');
ticker = 'someTicker';
rawTicker = polkadotMockUtils.createMockTicker(ticker);
procedureResult = entityMockUtils.getSecurityTokenInstance();
});

let addTransactionStub: sinon.SinonStub;

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

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

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

test('should throw an error if the user has not passed any arguments', () => {
const proc = procedureMockUtils.getInstance<Params, SecurityToken>();
proc.context = mockContext;

return expect(prepareModifyToken.call(proc, ({} as unknown) as Params)).rejects.toThrow(
'Nothing to modify'
);
});

test('should throw an error if the user is not the owner of the token', () => {
entityMockUtils.getSecurityTokenDetailsStub({
owner: entityMockUtils.getIdentityInstance({ did: 'someOtherDid' }),
});

const proc = procedureMockUtils.getInstance<Params, SecurityToken>();
proc.context = mockContext;

return expect(
prepareModifyToken.call(proc, {
ticker,
makeDivisible: true,
})
).rejects.toThrow(
'You must be the owner of the Security Token to modify any of its properties'
);
});

test('should throw an error if makeDivisible is set to true and the security token is already divisible', () => {
entityMockUtils.getSecurityTokenDetailsStub({
isDivisible: true,
});

const proc = procedureMockUtils.getInstance<Params, SecurityToken>();
proc.context = mockContext;

return expect(
prepareModifyToken.call(proc, {
ticker,
makeDivisible: true,
})
).rejects.toThrow('The Security Token is already divisible');
});

test('should throw an error if makeDivisible is set to false', () => {
const proc = procedureMockUtils.getInstance<Params, SecurityToken>();
proc.context = mockContext;

return expect(
prepareModifyToken.call(proc, {
ticker,
makeDivisible: (false as unknown) as true,
})
).rejects.toThrow('You cannot make the token indivisible');
});

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

const transaction = polkadotMockUtils.createTxStub('asset', 'makeDivisible');

const result = await prepareModifyToken.call(proc, {
ticker,
makeDivisible: true,
});

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

expect(result.ticker).toBe(procedureResult.ticker);
});
});
1 change: 1 addition & 0 deletions src/api/procedures/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { reserveTicker, ReserveTickerParams } from './reserveTicker';
export { modifyToken, ModifyTokenParams } from './modifyToken';
70 changes: 70 additions & 0 deletions src/api/procedures/modifyToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { SecurityToken } from '~/api/entities';
import { PolymeshError, Procedure } from '~/base';
import { ErrorCode } from '~/types';
import { stringToTicker } from '~/utils';

export type ModifyTokenParams =
| { makeDivisible?: true; name: string }
| { makeDivisible: true; name?: string };

export type Params = { ticker: string } & ModifyTokenParams;

/**
* @hidden
*/
export async function prepareModifyToken(
this: Procedure<Params, SecurityToken>,
args: Params
): Promise<SecurityToken> {
const {
context: {
polymeshApi: { tx },
},
context,
} = this;
const { ticker, makeDivisible } = args;

if (makeDivisible === undefined) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'Nothing to modify',
});
}

const rawTicker = stringToTicker(ticker, context);

const securityToken = new SecurityToken({ ticker }, context);

const { isDivisible, owner } = await securityToken.details();

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (owner.did !== context.currentIdentity!.did) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'You must be the owner of the Security Token to modify any of its properties',
});
}

if (makeDivisible) {
if (isDivisible) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'The Security Token is already divisible',
});
}

this.addTransaction(tx.asset.makeDivisible, {}, rawTicker);
} else {
/* istanbul ignore else: it does not apply to our business logic. this line will be remove in future task */
if (makeDivisible === false) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'You cannot make the token indivisible',
});
}
}

return securityToken;
}

export const modifyToken = new Procedure(prepareModifyToken);
2 changes: 1 addition & 1 deletion src/polkadot/augment-api-tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
import { Keys } from '@polkadot/types/interfaces/session';
import { EraIndex, RewardDestination, ValidatorPrefs } from '@polkadot/types/interfaces/staking';
import { Key } from '@polkadot/types/interfaces/system';
import { bool, Bytes, u16, u32, u64,u128 } from '@polkadot/types/primitive';
import { bool, Bytes, u16, u32, u64, u128 } from '@polkadot/types/primitive';
import { AnyNumber, ITuple } from '@polkadot/types/types';
import {
AccountKey,
Expand Down
2 changes: 1 addition & 1 deletion src/polkadot/polymesh/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { Enum, Option, Struct, U8aFixed, Vec } from '@polkadot/types/codec';
import { Signature } from '@polkadot/types/interfaces/extrinsics';
import { Balance, Call, H256, H512, Hash, Moment } from '@polkadot/types/interfaces/runtime';
import { bool, Bytes, Text, u8,u16, u32, u64, u128 } from '@polkadot/types/primitive';
import { bool, Bytes, Text, u8, u16, u32, u64, u128 } from '@polkadot/types/primitive';
import { ITuple } from '@polkadot/types/types';

/** @name AccountKey */
Expand Down
Loading

0 comments on commit 5d87d8b

Please sign in to comment.