From 3c8be1bb1b41c280e5f2b045b8da9932b48a8070 Mon Sep 17 00:00:00 2001 From: Jeremias Diaz Date: Tue, 24 Aug 2021 22:18:46 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20allow=20disabling=20IU?= =?UTF-8?q?=20when=20creating=20a=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `TickerReservation.create` endpoint now accepts an optional `requireInvestorUniqueness` parameter (defaults to true). This value is now also returned from `SecurityTokenDetails`. Also fetching the token's name from the new 3.3.0 query BREAKING CHANGE: 🧨 the `SecurityTokenDetails` interface now has a new `requireInvestorUniqueness: boolean` property. This affects the return value of `SecurityToken.details` --- .../entities/SecurityToken/__tests__/index.ts | 21 +++++++++++-- src/api/entities/SecurityToken/index.ts | 30 ++++++++++++++----- src/api/entities/SecurityToken/types.ts | 4 +++ .../__tests__/createSecurityToken.ts | 9 ++++-- src/api/procedures/createSecurityToken.ts | 9 +++++- 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/api/entities/SecurityToken/__tests__/index.ts b/src/api/entities/SecurityToken/__tests__/index.ts index 35e7f1852b..e8c1e70297 100644 --- a/src/api/entities/SecurityToken/__tests__/index.ts +++ b/src/api/entities/SecurityToken/__tests__/index.ts @@ -1,8 +1,9 @@ +import { bool, u64 } from '@polkadot/types'; import { Balance } from '@polkadot/types/interfaces'; -import { bool, u64 } from '@polkadot/types/primitive'; import BigNumber from 'bignumber.js'; import { AssetIdentifier, + AssetName, FundingRoundName, SecurityToken as MeshSecurityToken, } from 'polymesh-types/types'; @@ -76,20 +77,24 @@ describe('SecurityToken class', () => { let isDivisible: boolean; let owner: string; let assetType: 'EquityCommon'; + let iuDisabled: boolean; let did: string; let rawToken: MeshSecurityToken; + let rawName: AssetName; + let rawIuDisabled: bool; let context: Context; let securityToken: SecurityToken; beforeAll(() => { ticker = 'FAKETICKER'; - name = 'placeholder'; + name = 'tokenName'; totalSupply = 1000; isDivisible = true; owner = '0x0wn3r'; assetType = 'EquityCommon'; + iuDisabled = false; did = 'someDid'; }); @@ -102,6 +107,8 @@ describe('SecurityToken class', () => { total_supply: dsMockUtils.createMockBalance(totalSupply), /* eslint-enable @typescript-eslint/naming-convention */ }); + rawIuDisabled = dsMockUtils.createMockBool(iuDisabled); + rawName = dsMockUtils.createMockAssetName(name); context = dsMockUtils.getContextInstance(); securityToken = new SecurityToken({ ticker }, context); @@ -121,6 +128,12 @@ describe('SecurityToken class', () => { ), ], }); + dsMockUtils.createQueryStub('asset', 'assetNames', { + returnValue: rawName, + }); + dsMockUtils.createQueryStub('asset', 'disableInvestorUniqueness', { + returnValue: rawIuDisabled, + }); }); test('should return details for a security token', async () => { @@ -139,6 +152,7 @@ describe('SecurityToken class', () => { expect(details.assetType).toBe(assetType); expect(details.primaryIssuanceAgents).toEqual([entityMockUtils.getIdentityInstance({ did })]); expect(details.fullAgents).toEqual([entityMockUtils.getIdentityInstance({ did: owner })]); + expect(details.requiresInvestorUniqueness).toBe(true); dsMockUtils.createQueryStub('externalAgents', 'groupOfAgent', { entries: [ @@ -194,11 +208,12 @@ describe('SecurityToken class', () => { sinon.match({ assetType, isDivisible, - name: 'placeholder', + name, owner: sinon.match({ did: owner }), totalSupply: new BigNumber(totalSupply).div(Math.pow(10, 6)), primaryIssuanceAgents: [entityMockUtils.getIdentityInstance({ did })], fullAgents: [entityMockUtils.getIdentityInstance({ did: owner })], + requiresInvestorUniqueness: true, }) ); }); diff --git a/src/api/entities/SecurityToken/index.ts b/src/api/entities/SecurityToken/index.ts index f3c696476e..76124d3e16 100644 --- a/src/api/entities/SecurityToken/index.ts +++ b/src/api/entities/SecurityToken/index.ts @@ -1,6 +1,7 @@ -import { Option, StorageKey } from '@polkadot/types'; +import { bool, Option, StorageKey } from '@polkadot/types'; import { AgentGroup, + AssetName, Counter, IdentityId, SecurityToken as MeshSecurityToken, @@ -46,6 +47,7 @@ import { middlewareEventToEventIdentifier, numberToU32, stringToTicker, + textToString, tickerToDid, u64ToBigNumber, } from '~/utils/conversion'; @@ -183,7 +185,7 @@ export class SecurityToken extends Entity { public modify: ProcedureMethod; /** - * Retrieve the Security Token's name, total supply, whether it is divisible or not and the Identity of the owner + * Retrieve the Security Token's data * * @note can be subscribed to */ @@ -207,7 +209,9 @@ export class SecurityToken extends Entity { /* eslint-disable @typescript-eslint/naming-convention */ const assembleResult = async ( { total_supply, divisible, owner_did, asset_type }: MeshSecurityToken, - agentGroups: [StorageKey<[Ticker, IdentityId]>, Option][] + agentGroups: [StorageKey<[Ticker, IdentityId]>, Option][], + assetName: AssetName, + iuDisabled: bool ): Promise => { const primaryIssuanceAgents: Identity[] = []; const fullAgents: Identity[] = []; @@ -237,11 +241,12 @@ export class SecurityToken extends Entity { return { assetType, isDivisible: boolToBoolean(divisible), - name: 'placeholder', + name: textToString(assetName), owner, totalSupply: balanceToBigNumber(total_supply), primaryIssuanceAgents, fullAgents, + requiresInvestorUniqueness: !boolToBoolean(iuDisabled), }; }; /* eslint-enable @typescript-eslint/naming-convention */ @@ -249,19 +254,28 @@ export class SecurityToken extends Entity { const rawTicker = stringToTicker(ticker, context); const groupOfAgentPromise = externalAgents.groupOfAgent.entries(rawTicker); + const namePromise = asset.assetNames(rawTicker); + const disabledIuPromise = asset.disableInvestorUniqueness(rawTicker); if (callback) { - const groupOfAgents = await groupOfAgentPromise; + const groups = await groupOfAgentPromise; + const name = await namePromise; + const disabledIu = await disabledIuPromise; return asset.tokens(rawTicker, async securityToken => { - const result = await assembleResult(securityToken, groupOfAgents); + const result = await assembleResult(securityToken, groups, name, disabledIu); callback(result); }); } - const [token, groupOfAgent] = await Promise.all([asset.tokens(rawTicker), groupOfAgentPromise]); + const [token, groups, name, disabledIu] = await Promise.all([ + asset.tokens(rawTicker), + groupOfAgentPromise, + namePromise, + disabledIuPromise, + ]); - return assembleResult(token, groupOfAgent); + return assembleResult(token, groups, name, disabledIu); } /** diff --git a/src/api/entities/SecurityToken/types.ts b/src/api/entities/SecurityToken/types.ts index 65444a3554..ea134eb63e 100644 --- a/src/api/entities/SecurityToken/types.ts +++ b/src/api/entities/SecurityToken/types.ts @@ -9,8 +9,12 @@ export interface SecurityTokenDetails { name: string; owner: Identity; totalSupply: BigNumber; + /** + * @deprecated + */ primaryIssuanceAgents: Identity[]; fullAgents: Identity[]; + requiresInvestorUniqueness: boolean; } /** diff --git a/src/api/procedures/__tests__/createSecurityToken.ts b/src/api/procedures/__tests__/createSecurityToken.ts index 7b5bb5eb05..0b1067f301 100644 --- a/src/api/procedures/__tests__/createSecurityToken.ts +++ b/src/api/procedures/__tests__/createSecurityToken.ts @@ -67,6 +67,7 @@ describe('createSecurityToken procedure', () => { let tokenType: string; let tokenIdentifiers: TokenIdentifier[]; let fundingRound: string; + let requireInvestorUniqueness: boolean; let documents: TokenDocument[]; let rawTicker: Ticker; let rawName: AssetName; @@ -117,6 +118,7 @@ describe('createSecurityToken procedure', () => { }, ]; fundingRound = 'Series A'; + requireInvestorUniqueness = true; documents = [ { name: 'someDocument', @@ -152,7 +154,7 @@ describe('createSecurityToken procedure', () => { }) ); rawFundingRound = dsMockUtils.createMockFundingRoundName(fundingRound); - rawDisableIu = dsMockUtils.createMockBool(false); + rawDisableIu = dsMockUtils.createMockBool(!requireInvestorUniqueness); args = { ticker, name, @@ -187,7 +189,7 @@ describe('createSecurityToken procedure', () => { numberToBalanceStub.withArgs(totalSupply, mockContext, isDivisible).returns(rawTotalSupply); stringToAssetNameStub.withArgs(name, mockContext).returns(rawName); booleanToBoolStub.withArgs(isDivisible, mockContext).returns(rawIsDivisible); - booleanToBoolStub.withArgs(false, mockContext).returns(rawDisableIu); + booleanToBoolStub.withArgs(!requireInvestorUniqueness, mockContext).returns(rawDisableIu); internalTokenTypeToAssetTypeStub .withArgs(tokenType as KnownTokenType, mockContext) .returns(rawType); @@ -271,6 +273,7 @@ describe('createSecurityToken procedure', () => { totalSupply: new BigNumber(0), tokenIdentifiers: undefined, fundingRound: undefined, + requireInvestorUniqueness: false, }); sinon.assert.calledWith( @@ -283,7 +286,7 @@ describe('createSecurityToken procedure', () => { rawType, [], null, - rawDisableIu + rawIsDivisible // disable IU = true ); const issueTransaction = dsMockUtils.createTxStub('asset', 'issue'); diff --git a/src/api/procedures/createSecurityToken.ts b/src/api/procedures/createSecurityToken.ts index e0463fc45c..5050eb4574 100644 --- a/src/api/procedures/createSecurityToken.ts +++ b/src/api/procedures/createSecurityToken.ts @@ -64,6 +64,11 @@ export interface CreateSecurityTokenParams { */ fundingRound?: string; documents?: TokenDocument[]; + /** + * whether this asset requires investors to have a Investor Uniqueness Claim in order + * to hold it. Optional, defaults to true. More information about Investor Uniqueness and PUIS [here](https://developers.polymesh.live/introduction/identity#polymesh-unique-identity-system-puis) + */ + requireInvestorUniqueness?: boolean; } /** @@ -113,6 +118,7 @@ export async function prepareCreateSecurityToken( tokenIdentifiers = [], fundingRound, documents, + requireInvestorUniqueness = true, } = args; const reservation = new TickerReservation({ ticker }, context); @@ -163,6 +169,7 @@ export async function prepareCreateSecurityToken( tokenIdentifierToAssetIdentifier(identifier, context) ); const rawFundingRound = fundingRound ? stringToFundingRoundName(fundingRound, context) : null; + const rawDisableIu = booleanToBool(!requireInvestorUniqueness, context); let fee: undefined | BigNumber; @@ -184,7 +191,7 @@ export async function prepareCreateSecurityToken( rawType, rawIdentifiers, rawFundingRound, - booleanToBool(false, context) + rawDisableIu ); if (totalSupply && totalSupply.gt(0)) {