Skip to content

Commit

Permalink
feat(sto): improve offering details
Browse files Browse the repository at this point in the history
Added total offering amount, total remaining tokens and refined status

BREAKING CHANGE: Sto status is now divided into `timing`, `sale` and `balance`
  • Loading branch information
monitz87 committed Feb 25, 2021
1 parent 6b0ac85 commit 666a39f
Show file tree
Hide file tree
Showing 17 changed files with 521 additions and 194 deletions.
23 changes: 15 additions & 8 deletions src/api/entities/SecurityToken/Offerings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export class Offerings extends Namespace<SecurityToken> {
/**
* Retrieve all of the Token's Offerings. Can be filtered using parameters
*
* @param opts.status - status of the offerings to fetch
* @param opts.status - status of the offerings to fetch. As long as the STO has one of the passed statuses, it will be returned
*/
public async get(opts: { status?: StoStatus } = {}): Promise<StoWithDetails[]> {
public async get(opts: { status?: Partial<StoStatus> } = {}): Promise<StoWithDetails[]> {
const {
parent: { ticker },
context: {
Expand All @@ -57,7 +57,9 @@ export class Offerings extends Namespace<SecurityToken> {
context,
} = this;

const { status: statusFilter } = opts;
const {
status: { timing: timingFilter, balance: balanceFilter, sale: saleFilter } = {},
} = opts;

const entries = await query.sto.fundraisers.entries(stringToTicker(ticker, context));

Expand All @@ -66,10 +68,15 @@ export class Offerings extends Namespace<SecurityToken> {
details: fundraiserToStoDetails(fundraiser.unwrap(), context),
}));

if (statusFilter) {
return stos.filter(({ details: { status } }) => status === statusFilter);
}

return stos;
return stos.filter(
({
details: {
status: { timing, sale, balance },
},
}) =>
(!timingFilter || timingFilter === timing) &&
(!saleFilter || saleFilter === sale) &&
(!balanceFilter || balanceFilter === balance)
);
}
}
26 changes: 22 additions & 4 deletions src/api/entities/SecurityToken/__tests__/Offerings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import sinon from 'sinon';
import { Context, launchSto, Namespace, SecurityToken, Sto, TransactionQueue } from '~/internal';
import { Fundraiser, Ticker } from '~/polkadot';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { StoDetails, StoStatus } from '~/types';
import { StoBalanceStatus, StoDetails, StoSaleStatus, StoTimingStatus } from '~/types';
import { tuple } from '~/types/utils';
import * as utilsConversionModule from '~/utils/conversion';

Expand Down Expand Up @@ -121,7 +121,13 @@ describe('Offerings class', () => {
tiers,
minInvestment,
venue,
status: StoStatus.Closed,
status: {
sale: StoSaleStatus.Closed,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
totalAmount: tiers[0].amount,
totalRemaining: tiers[0].remaining,
},
{
creator,
Expand All @@ -133,7 +139,13 @@ describe('Offerings class', () => {
tiers,
minInvestment,
venue,
status: StoStatus.Live,
status: {
sale: StoSaleStatus.Live,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
totalAmount: tiers[0].amount,
totalRemaining: tiers[0].remaining,
},
];
fundraisers = [
Expand Down Expand Up @@ -227,7 +239,13 @@ describe('Offerings class', () => {
});

test('should return offerings associated to the token filtered by status', async () => {
const result = await offerings.get({ status: StoStatus.Live });
const result = await offerings.get({
status: {
sale: StoSaleStatus.Live,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
});

expect(result[0].sto.id).toEqual(new BigNumber(2));
expect(result[0].details).toEqual(details[1]);
Expand Down
28 changes: 18 additions & 10 deletions src/api/entities/Sto/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { heartbeat, investments } from '~/middleware/queries';
import { InvestmentResult } from '~/middleware/types';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { StoDetails, StoStatus } from '~/types';
import { StoBalanceStatus, StoDetails, StoSaleStatus, StoTimingStatus } from '~/types';
import * as utilsConversionModule from '~/utils/conversion';

jest.mock(
Expand Down Expand Up @@ -97,7 +97,9 @@ describe('Sto class', () => {
const someDid = 'someDid';
const otherDid = 'otherDid';
const raisingCurrency = 'USD';
const tierNumber = new BigNumber(10);
const amount = new BigNumber(1000);
const price = new BigNumber(100);
const remaining = new BigNumber(700);
const date = new Date();
const minInvestmentValue = new BigNumber(1);

Expand All @@ -117,9 +119,9 @@ describe('Sto class', () => {
raising_asset: dsMockUtils.createMockTicker(raisingCurrency),
tiers: [
dsMockUtils.createMockFundraiserTier({
total: dsMockUtils.createMockBalance(tierNumber.toNumber()),
price: dsMockUtils.createMockBalance(tierNumber.toNumber()),
remaining: dsMockUtils.createMockBalance(tierNumber.toNumber()),
total: dsMockUtils.createMockBalance(amount.toNumber()),
price: dsMockUtils.createMockBalance(price.toNumber()),
remaining: dsMockUtils.createMockBalance(remaining.toNumber()),
}),
],
venue_id: dsMockUtils.createMockU64(1),
Expand All @@ -146,16 +148,22 @@ describe('Sto class', () => {
raisingCurrency,
tiers: [
{
amount: tierNumber.div(Math.pow(10, 6)),
price: tierNumber.div(Math.pow(10, 6)),
remaining: tierNumber.div(Math.pow(10, 6)),
amount: amount.shiftedBy(-6),
price: price.shiftedBy(-6),
remaining: remaining.shiftedBy(-6),
},
],
venue: new Venue({ id: new BigNumber(1) }, context),
start: date,
end: date,
status: StoStatus.Live,
minInvestment: minInvestmentValue.div(Math.pow(10, 6)),
status: {
sale: StoSaleStatus.Live,
timing: StoTimingStatus.Expired,
balance: StoBalanceStatus.Residual,
},
minInvestment: minInvestmentValue.shiftedBy(-6),
totalAmount: amount.shiftedBy(-6),
totalRemaining: remaining.shiftedBy(-6),
};

dsMockUtils.createQueryStub('sto', 'fundraisers', {
Expand Down
57 changes: 55 additions & 2 deletions src/api/entities/Sto/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,63 @@ import BigNumber from 'bignumber.js';

import { DefaultPortfolio, Identity, NumberedPortfolio, Venue } from '~/internal';

export enum StoStatus {
Live = 'Live',
export enum StoTimingStatus {
/**
* Start date not reached yet
*/
NotStarted = 'NotStarted',
/**
* Between start and end date
*/
Started = 'Started',
/**
* End date reached
*/
Expired = 'Expired',
}

export enum StoBalanceStatus {
/**
* There still are Security Tokens available for purchase
*/
Available = 'Available',
/**
* All Security Tokens in the offering have been sold
*/
SoldOut = 'SoldOut',
/**
* There are remaining tokens, but their added value is lower than the Offering's
* minimum investment, so they cannot be purchased. The offering should be manually closed
* to retrieve them
*/
Residual = 'Residual',
}

export enum StoSaleStatus {
/**
* Sale temporarily paused, can be resumed (unfrozen) by the PIA
*/
Frozen = 'Frozen',
/**
* Investments can be made
*/
Live = 'Live',
/**
* Sale was manually closed before the end date was reached
*/
ClosedEarly = 'ClosedEarly',
/**
* Sale was manually closed after the end date was reached
*/
Closed = 'Closed',
}

export interface StoStatus {
timing: StoTimingStatus;
balance: StoBalanceStatus;
sale: StoSaleStatus;
}

export interface StoTier {
amount: BigNumber;
price: BigNumber;
Expand All @@ -28,6 +79,8 @@ export interface StoDetails {
end: Date | null;
status: StoStatus;
minInvestment: BigNumber;
totalAmount: BigNumber;
totalRemaining: BigNumber;
}

export interface Investment {
Expand Down
16 changes: 12 additions & 4 deletions src/api/procedures/__tests__/closeSto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getAuthorization, Params, prepareCloseSto } from '~/api/procedures/clos
import { Context } from '~/internal';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { RoleType, StoStatus, TxTags } from '~/types';
import { RoleType, StoBalanceStatus, StoSaleStatus, StoTimingStatus, TxTags } from '~/types';
import { PolymeshTx } from '~/types/internal';
import * as utilsConversionModule from '~/utils/conversion';

Expand Down Expand Up @@ -33,7 +33,11 @@ describe('closeSto procedure', () => {
entityMockUtils.initMocks({
stoOptions: {
details: {
status: StoStatus.Live,
status: {
sale: StoSaleStatus.Live,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
},
},
});
Expand Down Expand Up @@ -70,11 +74,15 @@ describe('closeSto procedure', () => {
sinon.assert.calledWith(addTransactionStub, stopStoTransaction, {}, rawTicker, rawId);
});

test('should throw an error if the sto is already closed', async () => {
test('should throw an error if the STO is already closed', async () => {
entityMockUtils.configureMocks({
stoOptions: {
details: {
status: StoStatus.Closed,
status: {
sale: StoSaleStatus.Closed,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
},
},
});
Expand Down
62 changes: 37 additions & 25 deletions src/api/procedures/__tests__/investInSto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ import {
import { Context, DefaultPortfolio } from '~/internal';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { PortfolioBalance, PortfolioLike, RoleType, StoStatus } from '~/types';
import {
PortfolioBalance,
PortfolioLike,
RoleType,
StoBalanceStatus,
StoSaleStatus,
StoTimingStatus,
} from '~/types';
import { PortfolioId } from '~/types/internal';
import * as utilsConversionModule from '~/utils/conversion';

Expand Down Expand Up @@ -128,11 +135,15 @@ describe('investInSto procedure', () => {
dsMockUtils.cleanup();
});

test('should throw an error if the STO is not live', async () => {
test('should throw an error if the STO is not accepting investments', async () => {
entityMockUtils.configureMocks({
stoOptions: {
details: {
status: StoStatus.Closed,
status: {
sale: StoSaleStatus.Frozen,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
},
},
});
Expand All @@ -141,31 +152,20 @@ describe('investInSto procedure', () => {
fundingPortfolioId,
});

return expect(prepareInvestInSto.call(proc, args)).rejects.toThrow('The STO is not live');
});

test('should throw an error if the STO has already ended', async () => {
entityMockUtils.configureMocks({
stoOptions: {
details: {
status: StoStatus.Live,
end: new Date('1/1/2020'),
},
},
});
const proc = procedureMockUtils.getInstance<Params, void, Storage>(mockContext, {
purchasePortfolioId,
fundingPortfolioId,
});

return expect(prepareInvestInSto.call(proc, args)).rejects.toThrow('The STO has already ended');
return expect(prepareInvestInSto.call(proc, args)).rejects.toThrow(
'The STO is not accepting investments at the moment'
);
});

test('should throw an error if the minimum investment is not reached', async () => {
entityMockUtils.configureMocks({
stoOptions: {
details: {
status: StoStatus.Live,
status: {
sale: StoSaleStatus.Live,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
end: new Date('12/12/2030'),
minInvestment: new BigNumber(10),
tiers: [
Expand Down Expand Up @@ -203,7 +203,11 @@ describe('investInSto procedure', () => {
entityMockUtils.configureMocks({
stoOptions: {
details: {
status: StoStatus.Live,
status: {
sale: StoSaleStatus.Live,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
end: new Date('12/12/2030'),
minInvestment: new BigNumber(25),
tiers: [
Expand Down Expand Up @@ -241,7 +245,11 @@ describe('investInSto procedure', () => {
entityMockUtils.configureMocks({
stoOptions: {
details: {
status: StoStatus.Live,
status: {
sale: StoSaleStatus.Live,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
end: new Date('12/12/2030'),
minInvestment: new BigNumber(10),
tiers: [
Expand Down Expand Up @@ -281,7 +289,11 @@ describe('investInSto procedure', () => {
entityMockUtils.configureMocks({
stoOptions: {
details: {
status: StoStatus.Live,
status: {
sale: StoSaleStatus.Live,
timing: StoTimingStatus.Started,
balance: StoBalanceStatus.Available,
},
end: new Date('12/12/2030'),
minInvestment: new BigNumber(10),
tiers: [
Expand Down

0 comments on commit 666a39f

Please sign in to comment.