Skip to content

Commit

Permalink
feat: 🎸 Return balance info when applying incoming balance
Browse files Browse the repository at this point in the history
BREAKING CHANGE: 🧨 On applying incoming balance(s) for an account,
the method now returns details about the confidential asset,
the amount being applied and balance after amount is deposited.
- `confidentialAccounts.applyIncomingBalance` returns
`IncomingConfidentialAssetBalance`
- `confidentialAccounts.applyIncomingBalances` returns
`IncomingConfidentialAssetBalance[]`
  • Loading branch information
prashantasdeveloper committed Jul 26, 2024
1 parent 5a41127 commit 81e5c1d
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 38 deletions.
5 changes: 3 additions & 2 deletions src/api/client/ConfidentialAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ApplyIncomingConfidentialAssetBalancesParams,
ConfidentialProcedureMethod,
CreateConfidentialAccountParams,
IncomingConfidentialAssetBalance,
} from '~/types';
import { createConfidentialProcedureMethod } from '~/utils/internal';

Expand Down Expand Up @@ -84,14 +85,14 @@ export class ConfidentialAccounts {
*/
public applyIncomingBalance: ConfidentialProcedureMethod<
ApplyIncomingBalanceParams,
ConfidentialAccount
IncomingConfidentialAssetBalance
>;

/**
* Applies any incoming balance to a Confidential Account
*/
public applyIncomingBalances: ConfidentialProcedureMethod<
ApplyIncomingConfidentialAssetBalancesParams,
ConfidentialAccount
IncomingConfidentialAssetBalance[]
>;
}
15 changes: 15 additions & 0 deletions src/api/entities/ConfidentialAccount/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,18 @@ export interface ApplyIncomingConfidentialAssetBalancesParams {
*/
maxUpdates?: BigNumber;
}

export interface IncomingConfidentialAssetBalance {
/**
* Confidential Asset whose balance has been applied
*/
asset: ConfidentialAsset;
/**
* Encrypted amount that was applied
*/
amount: string;
/**
* Encrypted balance after the `amount` was applied
*/
balance: string;
}
75 changes: 61 additions & 14 deletions src/api/procedures/__tests__/applyIncomingAssetBalance.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { ISubmittableResult } from '@polkadot/types/types';
import { ErrorCode } from '@polymeshassociation/polymesh-sdk/types';
import * as utilsInternalModule from '@polymeshassociation/polymesh-sdk/utils/internal';
import { when } from 'jest-when';

import {
createIncomingAssetBalanceResolver,
getAuthorization,
prepareApplyIncomingBalance,
} from '~/api/procedures/applyIncomingAssetBalance';
import { ConfidentialAccount, Context, PolymeshError } from '~/internal';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { ApplyIncomingBalanceParams, ConfidentialAsset, TxTags } from '~/types';
import {
ApplyIncomingBalanceParams,
ConfidentialAsset,
IncomingConfidentialAssetBalance,
TxTags,
} from '~/types';
import * as utilsConversionModule from '~/utils/conversion';

describe('applyIncomingAssetBalance procedure', () => {
Expand Down Expand Up @@ -57,9 +65,10 @@ describe('applyIncomingAssetBalance procedure', () => {
});

it('should throw an error if no incoming balance is present', async () => {
const proc = procedureMockUtils.getInstance<ApplyIncomingBalanceParams, ConfidentialAccount>(
mockContext
);
const proc = procedureMockUtils.getInstance<
ApplyIncomingBalanceParams,
IncomingConfidentialAssetBalance
>(mockContext);

const expectedError = new PolymeshError({
code: ErrorCode.DataUnavailable,
Expand All @@ -72,9 +81,10 @@ describe('applyIncomingAssetBalance procedure', () => {
});

it('should throw an error if account is not owned by the signer', async () => {
const proc = procedureMockUtils.getInstance<ApplyIncomingBalanceParams, ConfidentialAccount>(
mockContext
);
const proc = procedureMockUtils.getInstance<
ApplyIncomingBalanceParams,
IncomingConfidentialAssetBalance
>(mockContext);

const expectedError = new PolymeshError({
code: ErrorCode.UnmetPrerequisite,
Expand Down Expand Up @@ -105,23 +115,25 @@ describe('applyIncomingAssetBalance procedure', () => {

it('should return a apply incoming balance transaction spec', async () => {
const transaction = dsMockUtils.createTxMock('confidentialAsset', 'applyIncomingBalance');
const proc = procedureMockUtils.getInstance<ApplyIncomingBalanceParams, ConfidentialAccount>(
mockContext
);
const proc = procedureMockUtils.getInstance<
ApplyIncomingBalanceParams,
IncomingConfidentialAssetBalance
>(mockContext);

const result = await prepareApplyIncomingBalance.call(proc, args);
expect(result).toEqual({
transaction,
args: [account.publicKey, rawAssetId],
resolver: expect.objectContaining({ publicKey: account.publicKey }),
resolver: expect.any(Function),
});
});

describe('getAuthorization', () => {
it('should return the appropriate roles and permissions', () => {
const proc = procedureMockUtils.getInstance<ApplyIncomingBalanceParams, ConfidentialAccount>(
mockContext
);
const proc = procedureMockUtils.getInstance<
ApplyIncomingBalanceParams,
IncomingConfidentialAssetBalance
>(mockContext);
const boundFunc = getAuthorization.bind(proc);

expect(boundFunc()).toEqual({
Expand All @@ -133,4 +145,39 @@ describe('applyIncomingAssetBalance procedure', () => {
});
});
});

describe('createIncomingAssetBalanceResolver', () => {
const filterEventRecordsSpy = jest.spyOn(utilsInternalModule, 'filterEventRecords');
const rawBalance = dsMockUtils.createMockElgamalCipherText('0xbalance');
const rawAmount = dsMockUtils.createMockElgamalCipherText('0xamount');

afterEach(() => {
filterEventRecordsSpy.mockReset();
});

beforeEach(() => {
filterEventRecordsSpy.mockReturnValue([
dsMockUtils.createMockIEvent([
'0xPublicKey',
'0x76702175d8cbe3a55a19734433351e25',
rawAmount,
rawBalance,
]),
]);
});

it('should return the new Confidential Transaction', () => {
const fakeContext = {} as Context;

const result = createIncomingAssetBalanceResolver(fakeContext)({} as ISubmittableResult);

expect(result).toEqual(
expect.objectContaining({
asset: expect.objectContaining({ id: '76702175-d8cb-e3a5-5a19-734433351e25' }),
amount: '0xamount',
balance: '0xbalance',
})
);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { u16 } from '@polkadot/types';
import { ISubmittableResult } from '@polkadot/types/types';
import { ErrorCode } from '@polymeshassociation/polymesh-sdk/types';
import * as utilsInternalModule from '@polymeshassociation/polymesh-sdk/utils/internal';
import BigNumber from 'bignumber.js';
import { when } from 'jest-when';

import {
createIncomingAssetBalancesResolver,
getAuthorization,
prepareApplyIncomingConfidentialAssetBalances,
} from '~/api/procedures/applyIncomingConfidentialAssetBalances';
import { Context, PolymeshError } from '~/internal';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { ApplyIncomingConfidentialAssetBalancesParams, ConfidentialAccount, TxTags } from '~/types';
import {
ApplyIncomingConfidentialAssetBalancesParams,
ConfidentialAccount,
IncomingConfidentialAssetBalance,
TxTags,
} from '~/types';
import * as utilsConversionModule from '~/utils/conversion';

describe('applyIncomingConfidentialAssetBalances procedure', () => {
Expand Down Expand Up @@ -65,7 +73,7 @@ describe('applyIncomingConfidentialAssetBalances procedure', () => {
it('should throw an error if account is not owned by the signer', async () => {
const proc = procedureMockUtils.getInstance<
ApplyIncomingConfidentialAssetBalancesParams,
ConfidentialAccount
IncomingConfidentialAssetBalance[]
>(mockContext);

const expectedError = new PolymeshError({
Expand Down Expand Up @@ -97,7 +105,7 @@ describe('applyIncomingConfidentialAssetBalances procedure', () => {
it('should throw an error if no incoming balances are present', async () => {
const proc = procedureMockUtils.getInstance<
ApplyIncomingConfidentialAssetBalancesParams,
ConfidentialAccount
IncomingConfidentialAssetBalance[]
>(mockContext);

const expectedError = new PolymeshError({
Expand All @@ -117,14 +125,14 @@ describe('applyIncomingConfidentialAssetBalances procedure', () => {
const transaction = dsMockUtils.createTxMock('confidentialAsset', 'applyIncomingBalances');
const proc = procedureMockUtils.getInstance<
ApplyIncomingConfidentialAssetBalancesParams,
ConfidentialAccount
IncomingConfidentialAssetBalance[]
>(mockContext);

let result = await prepareApplyIncomingConfidentialAssetBalances.call(proc, args);
expect(result).toEqual({
transaction,
args: [account.publicKey, rawMaxUpdates],
resolver: expect.objectContaining({ publicKey: account.publicKey }),
resolver: expect.any(Function),
});

// maxUpdates to equal all incoming balances length
Expand All @@ -141,15 +149,15 @@ describe('applyIncomingConfidentialAssetBalances procedure', () => {
expect(result).toEqual({
transaction,
args: [account.publicKey, rawNewMaxUpdates],
resolver: expect.objectContaining({ publicKey: account.publicKey }),
resolver: expect.any(Function),
});
});

describe('getAuthorization', () => {
it('should return the appropriate roles and permissions', () => {
const proc = procedureMockUtils.getInstance<
ApplyIncomingConfidentialAssetBalancesParams,
ConfidentialAccount
IncomingConfidentialAssetBalance[]
>(mockContext);
const boundFunc = getAuthorization.bind(proc);

Expand All @@ -162,4 +170,39 @@ describe('applyIncomingConfidentialAssetBalances procedure', () => {
});
});
});

describe('createIncomingAssetBalancesResolver', () => {
const filterEventRecordsSpy = jest.spyOn(utilsInternalModule, 'filterEventRecords');
const rawAmount = dsMockUtils.createMockElgamalCipherText('0xamount');
const rawBalance = dsMockUtils.createMockElgamalCipherText('0xbalance');

beforeEach(() => {
filterEventRecordsSpy.mockReturnValue([
dsMockUtils.createMockIEvent([
'0xPublicKey',
'0x76702175d8cbe3a55a19734433351e25',
rawAmount,
rawBalance,
]),
]);
});

afterEach(() => {
filterEventRecordsSpy.mockReset();
});

it('should return the new Confidential Transaction', () => {
const fakeContext = {} as Context;

const result = createIncomingAssetBalancesResolver(fakeContext)({} as ISubmittableResult);

expect(result[0]).toEqual(
expect.objectContaining({
asset: expect.objectContaining({ id: '76702175-d8cb-e3a5-5a19-734433351e25' }),
amount: '0xamount',
balance: '0xbalance',
})
);
});
});
});
30 changes: 23 additions & 7 deletions src/api/procedures/applyIncomingAssetBalance.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
import { ISubmittableResult } from '@polkadot/types/types';
import { ErrorCode } from '@polymeshassociation/polymesh-sdk/types';
import { TransactionSpec } from '@polymeshassociation/polymesh-sdk/types/internal';
import { filterEventRecords } from '@polymeshassociation/polymesh-sdk/utils/internal';

import { meshAccountDepositEventDataToIncomingAssetBalance } from '~/api/procedures/applyIncomingConfidentialAssetBalances';
import { ConfidentialProcedure } from '~/base/ConfidentialProcedure';
import { PolymeshError } from '~/internal';
import { Context, PolymeshError } from '~/internal';
import {
ApplyIncomingBalanceParams,
ConfidentialAccount,
ConfidentialProcedureAuthorization,
IncomingConfidentialAssetBalance,
TxTags,
} from '~/types';
import { ExtrinsicParams } from '~/types/internal';
import { serializeConfidentialAssetId } from '~/utils/conversion';
import { asConfidentialAccount, asConfidentialAsset } from '~/utils/internal';

/**
* @hidden
*/
export const createIncomingAssetBalanceResolver =
(context: Context) =>
(receipt: ISubmittableResult): IncomingConfidentialAssetBalance => {
const [{ data }] = filterEventRecords(receipt, 'confidentialAsset', 'AccountDeposit');
return meshAccountDepositEventDataToIncomingAssetBalance(data, context);
};

/**
* @hidden
*/
export async function prepareApplyIncomingBalance(
this: ConfidentialProcedure<ApplyIncomingBalanceParams, ConfidentialAccount>,
this: ConfidentialProcedure<ApplyIncomingBalanceParams, IncomingConfidentialAssetBalance>,
args: ApplyIncomingBalanceParams
): Promise<
TransactionSpec<ConfidentialAccount, ExtrinsicParams<'confidentialAsset', 'applyIncomingBalance'>>
TransactionSpec<
IncomingConfidentialAssetBalance,
ExtrinsicParams<'confidentialAsset', 'applyIncomingBalance'>
>
> {
const {
context: {
Expand Down Expand Up @@ -52,15 +68,15 @@ export async function prepareApplyIncomingBalance(
return {
transaction: confidentialAsset.applyIncomingBalance,
args: [account.publicKey, serializeConfidentialAssetId(asset.id)],
resolver: account,
resolver: createIncomingAssetBalanceResolver(context),
};
}

/**
* @hidden
*/
export function getAuthorization(
this: ConfidentialProcedure<ApplyIncomingBalanceParams, ConfidentialAccount>
this: ConfidentialProcedure<ApplyIncomingBalanceParams, IncomingConfidentialAssetBalance>
): ConfidentialProcedureAuthorization {
return {
permissions: {
Expand All @@ -76,5 +92,5 @@ export function getAuthorization(
*/
export const applyIncomingAssetBalance = (): ConfidentialProcedure<
ApplyIncomingBalanceParams,
ConfidentialAccount
IncomingConfidentialAssetBalance
> => new ConfidentialProcedure(prepareApplyIncomingBalance, getAuthorization);
Loading

0 comments on commit 81e5c1d

Please sign in to comment.