Skip to content

Commit

Permalink
fix: add validations into numberToBalance utils method
Browse files Browse the repository at this point in the history
  • Loading branch information
shuffledex committed Sep 30, 2020
1 parent 0dfb8ff commit 7d42544
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 66 deletions.
2 changes: 1 addition & 1 deletion src/api/entities/SecurityToken/__tests__/Settlements.ts
Expand Up @@ -21,7 +21,7 @@ describe('Settlements class', () => {
let settlements: Settlements;
let stringToAccountIdStub: SinonStub<[string, Context], AccountId>;
let stringToTickerStub: SinonStub<[string, Context], Ticker>;
let numberToBalanceStub: SinonStub<[number | BigNumber, Context], Balance>;
let numberToBalanceStub: sinon.SinonStub;
let portfolioIdToMeshPortfolioIdStub: sinon.SinonStub<[PortfolioId, Context], MeshPortfolioId>;
let rawAccountId: AccountId;
let rawTicker: Ticker;
Expand Down
4 changes: 2 additions & 2 deletions src/api/procedures/__tests__/createSecurityToken.ts
Expand Up @@ -44,7 +44,7 @@ jest.mock(
describe('createSecurityToken procedure', () => {
let mockContext: Mocked<Context>;
let stringToTickerStub: sinon.SinonStub<[string, Context], Ticker>;
let numberToBalanceStub: sinon.SinonStub<[number | BigNumber, Context], Balance>;
let numberToBalanceStub: sinon.SinonStub;
let stringToAssetNameStub: sinon.SinonStub<[string, Context], AssetName>;
let booleanToBoolStub: sinon.SinonStub<[boolean, Context], bool>;
let tokenTypeToAssetTypeStub: sinon.SinonStub<[TokenType, Context], AssetType>;
Expand Down Expand Up @@ -169,7 +169,7 @@ describe('createSecurityToken procedure', () => {
mockContext = dsMockUtils.getContextInstance();

stringToTickerStub.withArgs(ticker, mockContext).returns(rawTicker);
numberToBalanceStub.withArgs(totalSupply, mockContext).returns(rawTotalSupply);
numberToBalanceStub.withArgs(totalSupply, mockContext, isDivisible).returns(rawTotalSupply);
stringToAssetNameStub.withArgs(name, mockContext).returns(rawName);
booleanToBoolStub.withArgs(isDivisible, mockContext).returns(rawIsDivisible);
tokenTypeToAssetTypeStub.withArgs(tokenType, mockContext).returns(rawType);
Expand Down
44 changes: 5 additions & 39 deletions src/api/procedures/__tests__/issueTokens.ts
Expand Up @@ -14,7 +14,6 @@ import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mo
import { Mocked } from '~/testUtils/types';
import { RoleType } from '~/types';
import * as utilsModule from '~/utils';
import { MAX_DECIMALS } from '~/utils/constants';

jest.mock(
'~/api/entities/SecurityToken',
Expand All @@ -24,7 +23,7 @@ jest.mock(
describe('issueTokens procedure', () => {
let mockContext: Mocked<Context>;
let stringToTickerStub: sinon.SinonStub<[string, Context], Ticker>;
let numberToBalance: sinon.SinonStub<[number | BigNumber, Context], Balance>;
let numberToBalance: sinon.SinonStub;
let ticker: string;
let rawTicker: Ticker;
let amount: BigNumber;
Expand All @@ -46,7 +45,6 @@ describe('issueTokens procedure', () => {
beforeEach(() => {
mockContext = dsMockUtils.getContextInstance();
stringToTickerStub.withArgs(ticker, mockContext).returns(rawTicker);
numberToBalance.withArgs(amount, mockContext).returns(rawAmount);
addTransactionStub = procedureMockUtils.getAddTransactionStub();
});

Expand All @@ -62,41 +60,6 @@ describe('issueTokens procedure', () => {
dsMockUtils.cleanup();
});

test('should throw an error if security token is divisible and the amount exceeds six decimals', () => {
const args = {
amount: new BigNumber(50.1234567),
ticker,
};

entityMockUtils.configureMocks({
securityTokenOptions: {
details: {
isDivisible: true,
},
},
});

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

return expect(prepareIssueTokens.call(proc, args)).rejects.toThrow(
`Issuance amount cannot have more than ${MAX_DECIMALS} decimals`
);
});

test('should throw an error if security token is not divisible and the amount has decimals', () => {
const args = {
amount: new BigNumber(50.1),

ticker,
};

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

return expect(prepareIssueTokens.call(proc, args)).rejects.toThrow(
'Cannot issue decimal amount of an indivisible token'
);
});

test('should throw an error if token supply is bigger than the limit total supply', async () => {
const args = {
amount,
Expand Down Expand Up @@ -160,6 +123,7 @@ describe('issueTokens procedure', () => {
});

test('should add a issue transaction to the queue', async () => {
const isDivisible = true;
const args = {
amount,
ticker,
Expand All @@ -168,12 +132,14 @@ describe('issueTokens procedure', () => {
entityMockUtils.configureMocks({
securityTokenOptions: {
details: {
isDivisible: true,
isDivisible,
primaryIssuanceAgent: entityMockUtils.getIdentityInstance(),
},
},
});

numberToBalance.withArgs(amount, mockContext, isDivisible).returns(rawAmount);

const transaction = dsMockUtils.createTxStub('asset', 'issue');
const proc = procedureMockUtils.getInstance<IssueTokensParams, SecurityToken>(mockContext);

Expand Down
2 changes: 1 addition & 1 deletion src/api/procedures/createSecurityToken.ts
Expand Up @@ -86,7 +86,7 @@ export async function prepareCreateSecurityToken(
}

const rawTicker = stringToTicker(ticker, context);
const rawTotalSupply = numberToBalance(totalSupply, context);
const rawTotalSupply = numberToBalance(totalSupply, context, isDivisible);
const rawName = stringToAssetName(name, context);
const rawIsDivisible = booleanToBool(isDivisible, context);
const rawType = tokenTypeToAssetType(tokenType, context);
Expand Down
20 changes: 2 additions & 18 deletions src/api/procedures/issueTokens.ts
Expand Up @@ -4,7 +4,7 @@ import { SecurityToken } from '~/api/entities';
import { PolymeshError, Procedure } from '~/base';
import { ErrorCode, Role, RoleType } from '~/types';
import { numberToBalance, stringToTicker } from '~/utils';
import { MAX_DECIMALS, MAX_TOKEN_AMOUNT } from '~/utils/constants';
import { MAX_TOKEN_AMOUNT } from '~/utils/constants';

export interface IssueTokensParams {
amount: BigNumber;
Expand Down Expand Up @@ -32,22 +32,6 @@ export async function prepareIssueTokens(

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

if (isDivisible) {
if (amount.decimalPlaces() > MAX_DECIMALS) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: `Issuance amount cannot have more than ${MAX_DECIMALS} decimals`,
});
}
} else {
if (amount.decimalPlaces()) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'Cannot issue decimal amount of an indivisible token',
});
}
}

const supplyAfterMint = amount.plus(totalSupply);

if (supplyAfterMint.isGreaterThan(MAX_TOKEN_AMOUNT)) {
Expand All @@ -69,7 +53,7 @@ export async function prepareIssueTokens(
}

const rawTicker = stringToTicker(ticker, context);
const rawValue = numberToBalance(amount, context);
const rawValue = numberToBalance(amount, context, isDivisible);

this.addTransaction(asset.issue, {}, rawTicker, rawValue);

Expand Down
63 changes: 60 additions & 3 deletions src/utils/__tests__/index.ts
Expand Up @@ -50,7 +50,12 @@ import {
} from '~/types';
import { SignerType, SignerValue } from '~/types/internal';
import { tuple } from '~/types/utils';
import { MAX_BATCH_ELEMENTS, MAX_TICKER_LENGTH } from '~/utils/constants';
import {
MAX_BATCH_ELEMENTS,
MAX_DECIMALS,
MAX_TICKER_LENGTH,
MAX_TOKEN_AMOUNT,
} from '~/utils/constants';

import {
accountIdToString,
Expand Down Expand Up @@ -390,7 +395,7 @@ describe('numberToBalance and balanceToBigNumber', () => {
});

test('numberToBalance should convert a number to a polkadot Balance object', () => {
const value = new BigNumber(100);
let value = new BigNumber(100);
const fakeResult = ('100' as unknown) as Balance;
const context = dsMockUtils.getContextInstance();

Expand All @@ -399,11 +404,63 @@ describe('numberToBalance and balanceToBigNumber', () => {
.withArgs('Balance', value.multipliedBy(Math.pow(10, 6)).toString())
.returns(fakeResult);

const result = numberToBalance(value, context);
let result = numberToBalance(value, context);

expect(result).toBe(fakeResult);

value = new BigNumber(100.1);

dsMockUtils
.getCreateTypeStub()
.withArgs('Balance', value.multipliedBy(Math.pow(10, 6)).toString())
.returns(fakeResult);

result = numberToBalance(value, context, true);

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

test('numberToBalance should throw an error if the value exceeds the max token amount constant', () => {
const value = new BigNumber(Math.pow(20, 15));
const context = dsMockUtils.getContextInstance();

let error;

try {
numberToBalance(value, context, true);
} catch (err) {
error = err;
}

expect(error.message).toBe('The value exceed the amount limit allowed');
expect(error.data).toMatchObject({ currentValue: value, amountLimit: MAX_TOKEN_AMOUNT });
});

test('numberToBalance should throw an error if security token is divisible and the value exceeds the max decimals constant', () => {
const value = new BigNumber(50.1234567);
const context = dsMockUtils.getContextInstance();

let error;

try {
numberToBalance(value, context, true);
} catch (err) {
error = err;
}

expect(error.message).toBe('The value exceed the decimals limit allowed');
expect(error.data).toMatchObject({ currentValue: value, decimalsLimit: MAX_DECIMALS });
});

test('numberToBalance should throw an error if security token is not divisible and the value has decimals', () => {
const value = new BigNumber(50.1234567);
const context = dsMockUtils.getContextInstance();

expect(() => numberToBalance(value, context)).toThrow(
'The value cannot have decimals if the token is indivisible'
);
});

test('balanceToBigNumber should convert a polkadot Balance object to a BigNumber', () => {
const fakeResult = 100;
const balance = dsMockUtils.createMockBalance(fakeResult);
Expand Down
43 changes: 41 additions & 2 deletions src/utils/index.ts
Expand Up @@ -110,8 +110,10 @@ import {
DEFAULT_GQL_PAGE_SIZE,
IGNORE_CHECKSUM,
MAX_BATCH_ELEMENTS,
MAX_DECIMALS,
MAX_MODULE_LENGTH,
MAX_TICKER_LENGTH,
MAX_TOKEN_AMOUNT,
SS58_FORMAT,
} from '~/utils/constants';

Expand Down Expand Up @@ -477,10 +479,47 @@ export function authorizationDataToAuthorization(auth: AuthorizationData): Autho
/**
* @hidden
*/
export function numberToBalance(value: number | BigNumber, context: Context): Balance {
export function numberToBalance(
value: number | BigNumber,
context: Context,
divisible?: boolean
): Balance {
const rawValue = new BigNumber(value);

if (rawValue.isGreaterThan(MAX_TOKEN_AMOUNT)) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'The value exceed the amount limit allowed',
data: {
currentValue: rawValue,
amountLimit: MAX_TOKEN_AMOUNT,
},
});
}

if (divisible) {
if (rawValue.decimalPlaces() > MAX_DECIMALS) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'The value exceed the decimals limit allowed',
data: {
currentValue: rawValue,
decimalsLimit: MAX_DECIMALS,
},
});
}
} else {
if (rawValue.decimalPlaces()) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'The value cannot have decimals if the token is indivisible',
});
}
}

return context.polymeshApi.createType(
'Balance',
new BigNumber(value).multipliedBy(Math.pow(10, 6)).toString()
rawValue.multipliedBy(Math.pow(10, 6)).toString()
);
}

Expand Down

0 comments on commit 7d42544

Please sign in to comment.