Skip to content

Commit

Permalink
feat: add getTransactionFees to the api root
Browse files Browse the repository at this point in the history
Also exposed `isDidValid` from the root
  • Loading branch information
monitz87 committed Jul 15, 2020
1 parent 12694cb commit f9268cc
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 127 deletions.
20 changes: 20 additions & 0 deletions src/Polymesh.ts
Expand Up @@ -5,7 +5,9 @@ import { ApolloClient, ApolloQueryResult } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { setContext } from 'apollo-link-context';
import { HttpLink } from 'apollo-link-http';
import BigNumber from 'bignumber.js';
import { polymesh } from 'polymesh-types/definitions';
import { TxTag } from 'polymesh-types/types';

import { Identity, SecurityToken, TickerReservation } from '~/api/entities';
import {
Expand Down Expand Up @@ -361,6 +363,24 @@ export class Polymesh {
return this.context.getCurrentIdentity();
}

/**
* Return whether the supplied identity/DID exists
*/
public async isIdentityValid(args: { identity: Identity | string }): Promise<boolean> {
const invalid = await this.context.getInvalidDids([valueToDid(args.identity)]);

return !invalid.length;
}

/**
* Retrieve the protocol fees associated with running a specific transaction
*
* @param args.tag - transaction tag (i.e. TxTags.asset.CreateAsset or "asset.createAsset")
*/
public getTransactionFees(args: { tag: TxTag }): Promise<BigNumber> {
return this.context.getTransactionFees(args.tag);
}

/**
* Get the treasury wallet address
*/
Expand Down
46 changes: 46 additions & 0 deletions src/__tests__/Polymesh.ts
Expand Up @@ -10,6 +10,7 @@ import { modifyClaims, reserveTicker, transferPolyX } from '~/api/procedures';
import { TransactionQueue } from '~/base';
import { didsWithClaims } from '~/middleware/queries';
import { IdentityWithClaims } from '~/middleware/types';
import { TxTags } from '~/polkadot';
import { Polymesh } from '~/Polymesh';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import {
Expand Down Expand Up @@ -501,6 +502,51 @@ describe('Polymesh Class', () => {
});
});

describe('method: isIdentityValid', () => {
test('should return true if the supplied identity exists', async () => {
const did = 'someDid';
dsMockUtils.configureMocks({ contextOptions: { invalidDids: [] } });

const polymesh = await Polymesh.connect({
nodeUrl: 'wss://some.url',
accountUri: '//uri',
});

const result = await polymesh.isIdentityValid({ identity: did });

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

test('should return false if the supplied identity is invalid', async () => {
const did = 'someDid';
dsMockUtils.configureMocks({ contextOptions: { invalidDids: [did] } });

const polymesh = await Polymesh.connect({
nodeUrl: 'wss://some.url',
accountUri: '//uri',
});

const result = await polymesh.isIdentityValid({ identity: did });

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

describe('method: getTransactionFees', () => {
test('should return the fees associated to the supplied transaction', async () => {
dsMockUtils.configureMocks({ contextOptions: { transactionFee: new BigNumber(500) } });

const polymesh = await Polymesh.connect({
nodeUrl: 'wss://some.url',
accountUri: '//uri',
});

const fee = await polymesh.getTransactionFees({ tag: TxTags.asset.CreateAsset });

expect(fee).toEqual(new BigNumber(500));
});
});

describe('method: getTreasuryAddress', () => {
test('should return the Treasury module address', async () => {
const treasuryAddress = '5EYCAe5ijAx5xEfZdpCna3grUpY1M9M5vLUH5vpmwV1EnaYR';
Expand Down
73 changes: 28 additions & 45 deletions src/base/PolymeshTransaction.ts
@@ -1,7 +1,6 @@
import { AddressOrPair } from '@polkadot/api/types';
import { DispatchError } from '@polkadot/types/interfaces';
import { ISubmittableResult, RegistryError } from '@polkadot/types/types';
import { stringUpperFirst } from '@polkadot/util';
import BigNumber from 'bignumber.js';
import { EventEmitter } from 'events';
import { TxTag } from 'polymesh-types/types';
Expand All @@ -17,13 +16,8 @@ import {
PostTransactionValueArray,
TransactionSpec,
} from '~/types/internal';
import {
balanceToBigNumber,
posRatioToBigNumber,
stringToProtocolOp,
unwrapValue,
unwrapValues,
} from '~/utils';
import { balanceToBigNumber, unwrapValue, unwrapValues } from '~/utils';
import { BATCH_REGEX } from '~/utils/constants';

enum Event {
StatusChange = 'StatusChange',
Expand All @@ -48,12 +42,6 @@ export class PolymeshTransaction<Args extends unknown[], Values extends unknown[
*/
public receipt?: ISubmittableResult;

/**
* type of transaction represented by this instance for display purposes.
* If the transaction isn't defined at design time, the tag won't be set (will be empty string) until the transaction is about to be run
*/
public tag = '' as TxTag;

/**
* transaction hash (status: `Running`, `Succeeded`, `Failed`)
*/
Expand Down Expand Up @@ -82,6 +70,14 @@ export class PolymeshTransaction<Args extends unknown[], Values extends unknown[
*/
private tx: MaybePostTransactionValue<PolymeshTx<Args>>;

/**
* @hidden
*
* type of transaction represented by this instance for display purposes.
* If the transaction isn't defined at design time, the tag won't be set (will be empty string) until the transaction is about to be run
*/
private _tag = '' as TxTag;

/**
* @hidden
*
Expand Down Expand Up @@ -202,16 +198,7 @@ export class PolymeshTransaction<Args extends unknown[], Values extends unknown[
* previous transaction in the queue)
*/
public async getFees(): Promise<Fees | null> {
const {
tx,
args,
signer,
batchSize,
context: {
polymeshApi: { query },
},
context,
} = this;
const { tx, args, signer, batchSize, context } = this;
let { protocolFee } = this;

let unwrappedTx;
Expand All @@ -224,49 +211,45 @@ export class PolymeshTransaction<Args extends unknown[], Values extends unknown[
return null;
}

const [rawCoefficient, { partialFee }] = await Promise.all([
query.protocolFee.coefficient(),
unwrappedTx(...unwrappedArgs).paymentInfo(signer),
]);
const coefficient = posRatioToBigNumber(rawCoefficient);
const { partialFee } = await unwrappedTx(...unwrappedArgs).paymentInfo(signer);

if (!protocolFee) {
const { section: moduleName, method: extrinsicName } = unwrappedTx;
const batchRegex = RegExp('(b|s?B)atch');
const { method: extrinsicName } = unwrappedTx;

if (batchRegex.exec(extrinsicName) && !batchSize) {
if (BATCH_REGEX.exec(extrinsicName) && !batchSize) {
throw new PolymeshError({
code: ErrorCode.FatalError,
message:
'Did not set batch size for batch transaction. Please report this error to the Polymath team',
});
}

const protocolOp = `${stringUpperFirst(moduleName)}${stringUpperFirst(
extrinsicName.replace(batchRegex, '')
)}`;

try {
const rawFee = await query.protocolFee.baseFees(stringToProtocolOp(protocolOp, context));
protocolFee = balanceToBigNumber(rawFee);
} catch (err) {
protocolFee = new BigNumber(0);
}
protocolFee = await context.getTransactionFees(this.tag);
}

return {
protocol: protocolFee.multipliedBy(coefficient).multipliedBy(batchSize || 1),
protocol: protocolFee.multipliedBy(batchSize || 1),
gas: balanceToBigNumber(partialFee),
};
}

/**
* type of transaction represented by this instance for display purposes.
* If the transaction isn't defined at design time, the tag won't be set (will be empty string) until the transaction is about to be run
*/
public get tag(): TxTag {
this.setTag();

return this._tag;
}

/**
* @hidden
*
* Set transaction tag if available and it hasn't been set yet
*/
private setTag(): void {
if (this.tag) {
if (this._tag) {
return;
}

Expand All @@ -282,7 +265,7 @@ export class PolymeshTransaction<Args extends unknown[], Values extends unknown[
transaction = tx;
}

this.tag = `${transaction.section}.${transaction.method}` as TxTag;
this._tag = `${transaction.section}.${transaction.method}` as TxTag;
}

/**
Expand Down
69 changes: 9 additions & 60 deletions src/base/__tests__/PolymeshTransaction.ts
Expand Up @@ -4,9 +4,9 @@ import BigNumber from 'bignumber.js';
import sinon from 'sinon';

import { PostTransactionValue } from '~/base';
import { Context } from '~/context';
import { PosRatio, ProtocolOp } from '~/polkadot';
import { TxTags } from '~/polkadot';
import { dsMockUtils } from '~/testUtils/mocks';
import { MockContext } from '~/testUtils/mocks/dataSources';
import { TransactionStatus } from '~/types';
import { PostTransactionValueArray } from '~/types/internal';
import { tuple } from '~/types/utils';
Expand All @@ -17,7 +17,7 @@ import { PolymeshTransaction } from '../PolymeshTransaction';
const { delay } = utilsModule;

describe('Polymesh Transaction class', () => {
let context: Context;
let context: MockContext;

beforeAll(() => {
dsMockUtils.initMocks();
Expand Down Expand Up @@ -357,55 +357,21 @@ describe('Polymesh Transaction class', () => {
});

describe('method: getFees', () => {
let posRatioToBigNumberStub: sinon.SinonStub<[PosRatio], BigNumber>;
let balanceToBigNumberStub: sinon.SinonStub<[Balance], BigNumber>;
let stringToProtocolOpStub: sinon.SinonStub<[string, Context], ProtocolOp>;
let protocolOps: string[];
let protocolFees: number[];
let protocolFees: BigNumber[];
let gasFees: number[];
let numerator: number;
let denominator: number;
let rawCoefficient: PosRatio;
let rawProtocolFees: Balance[];
let rawGasFees: Balance[];
let coefficient: BigNumber;

beforeAll(() => {
posRatioToBigNumberStub = sinon.stub(utilsModule, 'posRatioToBigNumber');
balanceToBigNumberStub = sinon.stub(utilsModule, 'balanceToBigNumber');
stringToProtocolOpStub = sinon.stub(utilsModule, 'stringToProtocolOp');
protocolOps = ['AssetRegisterTicker', 'AssetCreateAsset'];
protocolFees = [250, 150];
protocolFees = [new BigNumber(250), new BigNumber(150)];
gasFees = [5, 10];
numerator = 4;
denominator = 2;
rawCoefficient = dsMockUtils.createMockPosRatio(numerator, denominator);
rawProtocolFees = protocolFees.map(dsMockUtils.createMockBalance);
rawGasFees = gasFees.map(dsMockUtils.createMockBalance);
coefficient = new BigNumber(numerator).dividedBy(new BigNumber(denominator));
});

beforeEach(() => {
dsMockUtils.createQueryStub('protocolFee', 'coefficient', {
returnValue: rawCoefficient,
});
dsMockUtils
.createQueryStub('protocolFee', 'baseFees')
.withArgs('AssetRegisterTicker')
.resolves(rawProtocolFees[0]);
dsMockUtils
.createQueryStub('protocolFee', 'baseFees')
.withArgs('AssetCreateAsset')
.resolves(rawProtocolFees[1]);
posRatioToBigNumberStub.withArgs(rawCoefficient).returns(coefficient);
protocolOps.forEach(protocolOp =>
stringToProtocolOpStub
.withArgs(protocolOp, context)
.returns((protocolOp as unknown) as ProtocolOp)
);
rawProtocolFees.forEach((rawProtocolFee, index) =>
balanceToBigNumberStub.withArgs(rawProtocolFee).returns(new BigNumber(protocolFees[index]))
);
context.getTransactionFees.withArgs(TxTags.asset.RegisterTicker).resolves(protocolFees[0]);
context.getTransactionFees.withArgs(TxTags.asset.CreateAsset).resolves(protocolFees[1]);
rawGasFees.forEach((rawGasFee, index) =>
balanceToBigNumberStub.withArgs(rawGasFee).returns(new BigNumber(gasFees[index]))
);
Expand All @@ -429,7 +395,7 @@ describe('Polymesh Transaction class', () => {

let result = await transaction.getFees();

expect(result?.protocol).toEqual(new BigNumber(500));
expect(result?.protocol).toEqual(new BigNumber(250));
expect(result?.gas).toEqual(new BigNumber(5));

transaction = new PolymeshTransaction(
Expand All @@ -444,24 +410,7 @@ describe('Polymesh Transaction class', () => {

result = await transaction.getFees();

expect(result?.protocol).toEqual(new BigNumber(300));
expect(result?.gas).toEqual(new BigNumber(10));

stringToProtocolOpStub.withArgs(protocolOps[1], context).throws(); // extrinsic without a fee

transaction = new PolymeshTransaction(
{
...txSpec,
fee: null,
tx: tx2,
args,
},
context
);

result = await transaction.getFees();

expect(result?.protocol).toEqual(new BigNumber(0));
expect(result?.protocol).toEqual(new BigNumber(150));
expect(result?.gas).toEqual(new BigNumber(10));
});

Expand Down
16 changes: 7 additions & 9 deletions src/base/__tests__/Procedure.ts
Expand Up @@ -5,7 +5,7 @@ import sinon from 'sinon';

import * as baseModule from '~/base';
import { Context } from '~/context';
import { PosRatio, ProtocolOp } from '~/polkadot';
import { PosRatio, ProtocolOp, TxTag, TxTags } from '~/polkadot';
import { dsMockUtils } from '~/testUtils/mocks';
import { KeyringPair, Role } from '~/types';
import { MaybePostTransactionValue } from '~/types/internal';
Expand Down Expand Up @@ -35,8 +35,8 @@ describe('Procedure class', () => {
describe('method: prepare', () => {
let posRatioToBigNumberStub: sinon.SinonStub<[PosRatio], BigNumber>;
let balanceToBigNumberStub: sinon.SinonStub<[Balance], BigNumber>;
let stringToProtocolOpStub: sinon.SinonStub<[string, Context], ProtocolOp>;
let protocolOps: string[];
let txTagToProtocolOpStub: sinon.SinonStub<[TxTag, Context], ProtocolOp>;
let txTags: TxTag[];
let fees: number[];
let rawCoefficient: PosRatio;
let rawFees: Balance[];
Expand All @@ -47,8 +47,8 @@ describe('Procedure class', () => {
beforeAll(() => {
posRatioToBigNumberStub = sinon.stub(utilsModule, 'posRatioToBigNumber');
balanceToBigNumberStub = sinon.stub(utilsModule, 'balanceToBigNumber');
stringToProtocolOpStub = sinon.stub(utilsModule, 'stringToProtocolOp');
protocolOps = ['AssetRegisterTicker', 'IdentityRegisterDid'];
txTagToProtocolOpStub = sinon.stub(utilsModule, 'txTagToProtocolOp');
txTags = [TxTags.asset.RegisterTicker, TxTags.identity.RegisterDid];
fees = [250, 0];
numerator = 7;
denominator = 3;
Expand All @@ -71,10 +71,8 @@ describe('Procedure class', () => {
.resolves(rawFees[1]);

posRatioToBigNumberStub.withArgs(rawCoefficient).returns(coefficient);
protocolOps.forEach(protocolOp =>
stringToProtocolOpStub
.withArgs(protocolOp, context)
.returns((protocolOp as unknown) as ProtocolOp)
txTags.forEach(txTag =>
txTagToProtocolOpStub.withArgs(txTag, context).returns((txTag as unknown) as ProtocolOp)
);

rawFees.forEach((rawFee, index) =>
Expand Down

0 comments on commit f9268cc

Please sign in to comment.