Skip to content

Commit

Permalink
feat: add portfolio support to settlements.canSettle
Browse files Browse the repository at this point in the history
Also improve docs
  • Loading branch information
monitz87 committed Nov 5, 2020
1 parent 088b63f commit 108a3bd
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 17 deletions.
4 changes: 3 additions & 1 deletion src/api/entities/SecurityToken/Compliance/Requirements.ts
Expand Up @@ -139,7 +139,9 @@ export class Requirements extends Namespace<SecurityToken> {
}

/**
* Check whether transferring from one Identity to another complies with all the requirements of this asset
* Check whether the sender and receiver Identities in a transfer comply with all the requirements of this asset
*
* @note this does not take balances into account
*
* @param args.from - sender Identity (optional, defaults to the current Identity)
* @param args.to - receiver Identity
Expand Down
30 changes: 20 additions & 10 deletions src/api/entities/SecurityToken/Settlements.ts
@@ -1,13 +1,13 @@
import BigNumber from 'bignumber.js';
import { CanTransferResult } from 'polymesh-types/types';

import { Identity, Namespace, SecurityToken } from '~/api/entities';
import { TransferStatus } from '~/types';
import { Namespace, SecurityToken } from '~/api/entities';
import { PortfolioLike, TransferStatus } from '~/types';
import {
canTransferResultToTransferStatus,
numberToBalance,
portfolioIdToMeshPortfolioId,
signerToString,
portfolioLikeToPortfolioId,
stringToAccountId,
stringToTicker,
} from '~/utils';
Expand All @@ -18,15 +18,20 @@ import { DUMMY_ACCOUNT_ID } from '~/utils/constants';
*/
export class Settlements extends Namespace<SecurityToken> {
/**
* Check whether it is possible to transfer a certain amount of this asset between two Identities
* Check whether it is possible to create a settlement instruction to transfer a certain amount of this asset between two Portfolios.
*
* @param args.from - sender Identity (optional, defaults to the current Identity)
* @param args.to - receiver Identity
* @note this takes locked tokens into account. For example, if portfolio A has 1000 tokens and this function is called to check if 700 of them can be
* transferred to portfolio B (assuming everything else checks out) the result will be success. If an instruction is created and authorized to transfer those 700 tokens,
* they would become locked. From that point, further calls to this function would yield failed results because of the funds being locked, even though they haven't been
* transferred yet
*
* @param args.from - sender Portfolio (optional, defaults to the current Identity's Default Portfolio)
* @param args.to - receiver Portfolio
* @param args.amount - amount of tokens to transfer
*/
public async canSettle(args: {
from?: string | Identity;
to: string | Identity;
from?: PortfolioLike;
to: PortfolioLike;
amount: BigNumber;
}): Promise<TransferStatus> {
const {
Expand Down Expand Up @@ -54,13 +59,18 @@ export class Settlements extends Namespace<SecurityToken> {
*/
const senderAddress = context.currentPair?.address || DUMMY_ACCOUNT_ID;

const [fromPortfolio, toPortfolio] = await Promise.all([
portfolioLikeToPortfolioId(from, context),
portfolioLikeToPortfolioId(to, context),
]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const res: CanTransferResult = await (rpc as any).asset.canTransfer(
stringToAccountId(senderAddress, context),
null,
portfolioIdToMeshPortfolioId({ did: signerToString(from) }, context),
portfolioIdToMeshPortfolioId(fromPortfolio, context),
null,
portfolioIdToMeshPortfolioId({ did: signerToString(to) }, context),
portfolioIdToMeshPortfolioId(toPortfolio, context),
stringToTicker(ticker, context),
numberToBalance(amount, context, isDivisible)
);
Expand Down
26 changes: 20 additions & 6 deletions src/api/entities/SecurityToken/__tests__/Settlements.ts
Expand Up @@ -7,7 +7,7 @@ import { Namespace } from '~/api/entities';
import { Context } from '~/base';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { TransferStatus } from '~/types';
import { PortfolioLike, TransferStatus } from '~/types';
import { PortfolioId } from '~/types/internal';
import * as utilsModule from '~/utils';
import { DUMMY_ACCOUNT_ID } from '~/utils/constants';
Expand All @@ -23,6 +23,10 @@ describe('Settlements class', () => {
let stringToTickerStub: SinonStub<[string, Context], Ticker>;
let numberToBalanceStub: sinon.SinonStub;
let portfolioIdToMeshPortfolioIdStub: sinon.SinonStub<[PortfolioId, Context], MeshPortfolioId>;
let portfolioLikeToPortfolioIdStub: sinon.SinonStub<
[PortfolioLike, Context],
Promise<PortfolioId>
>;
let rawAccountId: AccountId;
let rawTicker: Ticker;
let rawAmount: Balance;
Expand All @@ -42,6 +46,7 @@ describe('Settlements class', () => {
stringToTickerStub = sinon.stub(utilsModule, 'stringToTicker');
numberToBalanceStub = sinon.stub(utilsModule, 'numberToBalance');
portfolioIdToMeshPortfolioIdStub = sinon.stub(utilsModule, 'portfolioIdToMeshPortfolioId');
portfolioLikeToPortfolioIdStub = sinon.stub(utilsModule, 'portfolioLikeToPortfolioId');
rawAmount = dsMockUtils.createMockBalance(amount.toNumber());
});

Expand Down Expand Up @@ -73,26 +78,35 @@ describe('Settlements class', () => {

describe('method: canSettle', () => {
let fromDid: string;
let fromPortfolioId: PortfolioId;
let toPortfolioId: PortfolioId;
const rawFromPortfolio = dsMockUtils.createMockPortfolioId();
const rawToPortfolio = dsMockUtils.createMockPortfolioId();

beforeAll(() => {
fromDid = 'fromDid';
fromPortfolioId = { did: fromDid };
toPortfolioId = { did: toDid };
});

beforeEach(() => {
portfolioIdToMeshPortfolioIdStub
.withArgs({ did: toDid }, mockContext)
.returns(rawToPortfolio);
portfolioLikeToPortfolioIdStub.withArgs(fromDid, mockContext).resolves(fromPortfolioId);
portfolioLikeToPortfolioIdStub.withArgs(toDid, mockContext).resolves(toPortfolioId);
portfolioIdToMeshPortfolioIdStub.withArgs(toPortfolioId, mockContext).returns(rawToPortfolio);
});

test('should return a status value representing whether the transaction can be made from the current Identity', async () => {
const { did: currentDid } = await mockContext.getCurrentIdentity();
const currentIdentity = await mockContext.getCurrentIdentity();
const { did: currentDid } = currentIdentity;

const rawDummyAccountId = dsMockUtils.createMockAccountId(DUMMY_ACCOUNT_ID);
const currentDefaultPortfolioId = { did: currentDid };

portfolioLikeToPortfolioIdStub
.withArgs(currentIdentity, mockContext)
.resolves(currentDefaultPortfolioId);
portfolioIdToMeshPortfolioIdStub
.withArgs({ did: currentDid }, mockContext)
.withArgs(currentDefaultPortfolioId, mockContext)
.returns(rawFromPortfolio);

// also test the case where the SDK was instanced without an account
Expand Down

0 comments on commit 108a3bd

Please sign in to comment.