From 81e5c1d68cf6a4198d9be27066a1ff330ee7cf26 Mon Sep 17 00:00:00 2001 From: Prashant Bajpai <34747455+prashantasdeveloper@users.noreply.github.com> Date: Fri, 26 Jul 2024 18:22:23 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Return=20balance=20info?= =?UTF-8?q?=20when=20applying=20incoming=20balance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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[]` --- src/api/client/ConfidentialAccounts.ts | 5 +- src/api/entities/ConfidentialAccount/types.ts | 15 ++++ .../__tests__/applyIncomingAssetBalance.ts | 75 +++++++++++++++---- .../applyIncomingConfidentialAssetBalances.ts | 57 ++++++++++++-- .../procedures/applyIncomingAssetBalance.ts | 30 ++++++-- .../applyIncomingConfidentialAssetBalances.ts | 62 +++++++++++++-- 6 files changed, 206 insertions(+), 38 deletions(-) diff --git a/src/api/client/ConfidentialAccounts.ts b/src/api/client/ConfidentialAccounts.ts index e56e4ba7f6..b390e40f04 100644 --- a/src/api/client/ConfidentialAccounts.ts +++ b/src/api/client/ConfidentialAccounts.ts @@ -12,6 +12,7 @@ import { ApplyIncomingConfidentialAssetBalancesParams, ConfidentialProcedureMethod, CreateConfidentialAccountParams, + IncomingConfidentialAssetBalance, } from '~/types'; import { createConfidentialProcedureMethod } from '~/utils/internal'; @@ -84,7 +85,7 @@ export class ConfidentialAccounts { */ public applyIncomingBalance: ConfidentialProcedureMethod< ApplyIncomingBalanceParams, - ConfidentialAccount + IncomingConfidentialAssetBalance >; /** @@ -92,6 +93,6 @@ export class ConfidentialAccounts { */ public applyIncomingBalances: ConfidentialProcedureMethod< ApplyIncomingConfidentialAssetBalancesParams, - ConfidentialAccount + IncomingConfidentialAssetBalance[] >; } diff --git a/src/api/entities/ConfidentialAccount/types.ts b/src/api/entities/ConfidentialAccount/types.ts index 43b849dc0b..873fd8399c 100644 --- a/src/api/entities/ConfidentialAccount/types.ts +++ b/src/api/entities/ConfidentialAccount/types.ts @@ -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; +} diff --git a/src/api/procedures/__tests__/applyIncomingAssetBalance.ts b/src/api/procedures/__tests__/applyIncomingAssetBalance.ts index cef5dc559d..4aed4d781e 100644 --- a/src/api/procedures/__tests__/applyIncomingAssetBalance.ts +++ b/src/api/procedures/__tests__/applyIncomingAssetBalance.ts @@ -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', () => { @@ -57,9 +65,10 @@ describe('applyIncomingAssetBalance procedure', () => { }); it('should throw an error if no incoming balance is present', async () => { - const proc = procedureMockUtils.getInstance( - mockContext - ); + const proc = procedureMockUtils.getInstance< + ApplyIncomingBalanceParams, + IncomingConfidentialAssetBalance + >(mockContext); const expectedError = new PolymeshError({ code: ErrorCode.DataUnavailable, @@ -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( - mockContext - ); + const proc = procedureMockUtils.getInstance< + ApplyIncomingBalanceParams, + IncomingConfidentialAssetBalance + >(mockContext); const expectedError = new PolymeshError({ code: ErrorCode.UnmetPrerequisite, @@ -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( - 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( - mockContext - ); + const proc = procedureMockUtils.getInstance< + ApplyIncomingBalanceParams, + IncomingConfidentialAssetBalance + >(mockContext); const boundFunc = getAuthorization.bind(proc); expect(boundFunc()).toEqual({ @@ -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', + }) + ); + }); + }); }); diff --git a/src/api/procedures/__tests__/applyIncomingConfidentialAssetBalances.ts b/src/api/procedures/__tests__/applyIncomingConfidentialAssetBalances.ts index c5532468b9..1a8ca3c6f0 100644 --- a/src/api/procedures/__tests__/applyIncomingConfidentialAssetBalances.ts +++ b/src/api/procedures/__tests__/applyIncomingConfidentialAssetBalances.ts @@ -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', () => { @@ -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({ @@ -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({ @@ -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 @@ -141,7 +149,7 @@ describe('applyIncomingConfidentialAssetBalances procedure', () => { expect(result).toEqual({ transaction, args: [account.publicKey, rawNewMaxUpdates], - resolver: expect.objectContaining({ publicKey: account.publicKey }), + resolver: expect.any(Function), }); }); @@ -149,7 +157,7 @@ describe('applyIncomingConfidentialAssetBalances procedure', () => { it('should return the appropriate roles and permissions', () => { const proc = procedureMockUtils.getInstance< ApplyIncomingConfidentialAssetBalancesParams, - ConfidentialAccount + IncomingConfidentialAssetBalance[] >(mockContext); const boundFunc = getAuthorization.bind(proc); @@ -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', + }) + ); + }); + }); }); diff --git a/src/api/procedures/applyIncomingAssetBalance.ts b/src/api/procedures/applyIncomingAssetBalance.ts index 85278f4ae0..d4f8130a0c 100644 --- a/src/api/procedures/applyIncomingAssetBalance.ts +++ b/src/api/procedures/applyIncomingAssetBalance.ts @@ -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, + this: ConfidentialProcedure, args: ApplyIncomingBalanceParams ): Promise< - TransactionSpec> + TransactionSpec< + IncomingConfidentialAssetBalance, + ExtrinsicParams<'confidentialAsset', 'applyIncomingBalance'> + > > { const { context: { @@ -52,7 +68,7 @@ export async function prepareApplyIncomingBalance( return { transaction: confidentialAsset.applyIncomingBalance, args: [account.publicKey, serializeConfidentialAssetId(asset.id)], - resolver: account, + resolver: createIncomingAssetBalanceResolver(context), }; } @@ -60,7 +76,7 @@ export async function prepareApplyIncomingBalance( * @hidden */ export function getAuthorization( - this: ConfidentialProcedure + this: ConfidentialProcedure ): ConfidentialProcedureAuthorization { return { permissions: { @@ -76,5 +92,5 @@ export function getAuthorization( */ export const applyIncomingAssetBalance = (): ConfidentialProcedure< ApplyIncomingBalanceParams, - ConfidentialAccount + IncomingConfidentialAssetBalance > => new ConfidentialProcedure(prepareApplyIncomingBalance, getAuthorization); diff --git a/src/api/procedures/applyIncomingConfidentialAssetBalances.ts b/src/api/procedures/applyIncomingConfidentialAssetBalances.ts index 252722d6f3..0ccc8225d3 100644 --- a/src/api/procedures/applyIncomingConfidentialAssetBalances.ts +++ b/src/api/procedures/applyIncomingConfidentialAssetBalances.ts @@ -1,28 +1,71 @@ +import { + ConfidentialAssetsElgamalCipherText, + PalletConfidentialAssetConfidentialAccount, +} from '@polkadot/types/lookup'; +import { ISubmittableResult } from '@polkadot/types/types'; +import { U8aFixed } from '@polkadot/types-codec'; import { ErrorCode } from '@polymeshassociation/polymesh-sdk/types'; import { TransactionSpec } from '@polymeshassociation/polymesh-sdk/types/internal'; +import { filterEventRecords } from '@polymeshassociation/polymesh-sdk/utils/internal'; import { BigNumber } from 'bignumber.js'; import { ConfidentialProcedure } from '~/base/ConfidentialProcedure'; -import { PolymeshError } from '~/internal'; +import { ConfidentialAsset, Context, PolymeshError } from '~/internal'; import { ApplyIncomingConfidentialAssetBalancesParams, - ConfidentialAccount, ConfidentialProcedureAuthorization, + IncomingConfidentialAssetBalance, TxTags, } from '~/types'; import { ExtrinsicParams } from '~/types/internal'; -import { bigNumberToU16 } from '~/utils/conversion'; +import { bigNumberToU16, meshConfidentialAssetToAssetId } from '~/utils/conversion'; import { asConfidentialAccount } from '~/utils/internal'; +/** + * @hidden + */ +export const meshAccountDepositEventDataToIncomingAssetBalance = ( + data: [ + PalletConfidentialAssetConfidentialAccount, + U8aFixed, + ConfidentialAssetsElgamalCipherText, + ConfidentialAssetsElgamalCipherText + ], + context: Context +): IncomingConfidentialAssetBalance => { + const [, rawAssetId, rawAmount, rawBalance] = data; + const id = meshConfidentialAssetToAssetId(rawAssetId); + return { + asset: new ConfidentialAsset({ id }, context), + amount: rawAmount.toString(), + balance: rawBalance.toString(), + }; +}; + +/** + * @hidden + */ +export const createIncomingAssetBalancesResolver = + (context: Context) => + (receipt: ISubmittableResult): IncomingConfidentialAssetBalance[] => { + const accountDeposits = filterEventRecords(receipt, 'confidentialAsset', 'AccountDeposit'); + return accountDeposits.map(({ data }) => + meshAccountDepositEventDataToIncomingAssetBalance(data, context) + ); + }; + /** * @hidden */ export async function prepareApplyIncomingConfidentialAssetBalances( - this: ConfidentialProcedure, + this: ConfidentialProcedure< + ApplyIncomingConfidentialAssetBalancesParams, + IncomingConfidentialAssetBalance[] + >, args: ApplyIncomingConfidentialAssetBalancesParams ): Promise< TransactionSpec< - ConfidentialAccount, + IncomingConfidentialAssetBalance[], ExtrinsicParams<'confidentialAsset', 'applyIncomingBalances'> > > { @@ -71,7 +114,7 @@ export async function prepareApplyIncomingConfidentialAssetBalances( return { transaction: confidentialAsset.applyIncomingBalances, args: [account.publicKey, rawMaxUpdates], - resolver: account, + resolver: createIncomingAssetBalancesResolver(context), }; } @@ -79,7 +122,10 @@ export async function prepareApplyIncomingConfidentialAssetBalances( * @hidden */ export function getAuthorization( - this: ConfidentialProcedure + this: ConfidentialProcedure< + ApplyIncomingConfidentialAssetBalancesParams, + IncomingConfidentialAssetBalance[] + > ): ConfidentialProcedureAuthorization { return { permissions: { @@ -95,5 +141,5 @@ export function getAuthorization( */ export const applyIncomingConfidentialAssetBalances = (): ConfidentialProcedure< ApplyIncomingConfidentialAssetBalancesParams, - ConfidentialAccount + IncomingConfidentialAssetBalance[] > => new ConfidentialProcedure(prepareApplyIncomingConfidentialAssetBalances, getAuthorization);