Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 🎸 Return balance info when applying incoming balance #30

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading