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 #25

Merged
merged 2 commits 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@polymeshassociation/fireblocks-signing-manager": "^2.3.0",
"@polymeshassociation/hashicorp-vault-signing-manager": "^3.1.0",
"@polymeshassociation/local-signing-manager": "^3.1.0",
"@polymeshassociation/polymesh-private-sdk": "1.2.0-alpha.2",
"@polymeshassociation/polymesh-private-sdk": "2.0.0-alpha.1",
"@polymeshassociation/signing-manager-types": "^3.1.0",
"class-transformer": "0.5.1",
"class-validator": "^0.14.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ import {
ConfidentialAccount,
ConfidentialAssetHistoryEntry,
EventIdEnum,
IncomingConfidentialAssetBalance,
ResultSet,
TxTags,
} from '@polymeshassociation/polymesh-private-sdk/types';

import { ConfidentialAccountsController } from '~/confidential-accounts/confidential-accounts.controller';
import { ConfidentialAccountsService } from '~/confidential-accounts/confidential-accounts.service';
import { AppliedConfidentialAssetBalanceModel } from '~/confidential-accounts/models/applied-confidential-asset-balance.model';
import { AppliedConfidentialAssetBalancesModel } from '~/confidential-accounts/models/applied-confidential-asset-balances.model';
import { ConfidentialTransactionHistoryModel } from '~/confidential-accounts/models/confidential-transaction-history.model';
import { PaginatedResultsModel } from '~/polymesh-rest-api/src/common/models/paginated-results.model';
import { ServiceReturn } from '~/polymesh-rest-api/src/common/utils/functions';
import { testValues } from '~/test-utils/consts';
import { createMockConfidentialAsset, createMockIdentity } from '~/test-utils/mocks';
import { getMockTransaction, testValues } from '~/test-utils/consts';
import {
createMockConfidentialAsset,
createMockIdentity,
createMockTransactionResult,
} from '~/test-utils/mocks';
import { mockConfidentialAccountsServiceProvider } from '~/test-utils/service-mocks';

const { signer, txResult } = testValues;
Expand Down Expand Up @@ -130,12 +138,37 @@ describe('ConfidentialAccountsController', () => {
const input = {
signer,
};
mockConfidentialAccountsService.applyAllIncomingAssetBalances.mockResolvedValue(
txResult as unknown as ServiceReturn<ConfidentialAccount>
);
const mockAsset = createMockConfidentialAsset();
const mockIncomingAssetBalances = [
{
asset: mockAsset,
amount: '0xamount',
balance: '0xbalance',
},
];
const transaction = getMockTransaction(TxTags.confidentialAsset.ApplyIncomingBalances);

const testTxResult = createMockTransactionResult<IncomingConfidentialAssetBalance[]>({
...txResult,
transactions: [transaction],
result: mockIncomingAssetBalances,
});
mockConfidentialAccountsService.applyAllIncomingAssetBalances.mockResolvedValue(testTxResult);

const result = await controller.applyAllIncomingAssetBalances({ confidentialAccount }, input);
expect(result).toEqual(txResult);
expect(result).toEqual(
new AppliedConfidentialAssetBalancesModel({
...txResult,
transactions: [transaction],
appliedAssetBalances: [
new AppliedConfidentialAssetBalanceModel({
confidentialAsset: mockAsset.id,
amount: mockIncomingAssetBalances[0].amount,
balance: mockIncomingAssetBalances[0].balance,
}),
],
})
);
});
});

Expand Down
22 changes: 20 additions & 2 deletions src/confidential-accounts/confidential-accounts.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import {
ApiTags,
} from '@nestjs/swagger';
import { BigNumber } from '@polymeshassociation/polymesh-private-sdk';
import { IncomingConfidentialAssetBalance } from '@polymeshassociation/polymesh-private-sdk/types';

import { ConfidentialAccountsService } from '~/confidential-accounts/confidential-accounts.service';
import { ConfidentialAccountParamsDto } from '~/confidential-accounts/dto/confidential-account-params.dto';
import { TransactionHistoryParamsDto } from '~/confidential-accounts/dto/transaction-history-params.dto';
import { AppliedConfidentialAssetBalanceModel } from '~/confidential-accounts/models/applied-confidential-asset-balance.model';
import { AppliedConfidentialAssetBalancesModel } from '~/confidential-accounts/models/applied-confidential-asset-balances.model';
import { ConfidentialAssetBalanceModel } from '~/confidential-accounts/models/confidential-asset-balance.model';
import { ConfidentialTransactionHistoryModel } from '~/confidential-accounts/models/confidential-transaction-history.model';
import { ConfidentialAssetIdParamsDto } from '~/confidential-assets/dto/confidential-asset-id-params.dto';
Expand All @@ -26,6 +29,7 @@ import { PaginatedResultsModel } from '~/polymesh-rest-api/src/common/models/pag
import { TransactionQueueModel } from '~/polymesh-rest-api/src/common/models/transaction-queue.model';
import {
handleServiceResult,
TransactionResolver,
TransactionResponseModel,
} from '~/polymesh-rest-api/src/common/utils/functions';

Expand Down Expand Up @@ -240,7 +244,7 @@ export class ConfidentialAccountsController {
})
@ApiTransactionResponse({
description: 'Details about the transaction',
type: TransactionQueueModel,
type: AppliedConfidentialAssetBalancesModel,
})
@ApiTransactionFailedResponse({
[HttpStatus.UNPROCESSABLE_ENTITY]: [
Expand All @@ -257,7 +261,21 @@ export class ConfidentialAccountsController {
params
);

return handleServiceResult(result);
const resolver: TransactionResolver<IncomingConfidentialAssetBalance[]> = ({
result: appliedAssetBalances,
transactions,
details,
}) =>
new AppliedConfidentialAssetBalancesModel({
appliedAssetBalances: appliedAssetBalances.map(
({ asset: { id: confidentialAsset }, amount, balance }) =>
new AppliedConfidentialAssetBalanceModel({ confidentialAsset, amount, balance })
),
transactions,
details,
});

return handleServiceResult(result, resolver);
}

@ApiOperation({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,17 +253,23 @@ describe('ConfidentialAccountsService', () => {
tag: TxTags.confidentialAsset.ApplyIncomingBalances,
};
const mockTransaction = new MockTransaction(mockTransactions);
const mockAccount = createMockConfidentialAccount();
const mockIncomingAssetBalances = [
{
asset: createMockConfidentialAsset(),
amount: '0xAmount',
balance: '0xBalance',
},
];

mockTransactionsService.submit.mockResolvedValue({
result: mockAccount,
result: mockIncomingAssetBalances,
transactions: [mockTransaction],
});

const result = await service.applyAllIncomingAssetBalances(confidentialAccount, input);

expect(result).toEqual({
result: mockAccount,
result: mockIncomingAssetBalances,
transactions: [mockTransaction],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ConfidentialTransaction,
EventIdEnum,
Identity,
IncomingConfidentialAssetBalance,
ResultSet,
} from '@polymeshassociation/polymesh-private-sdk/types';

Expand Down Expand Up @@ -106,7 +107,7 @@ export class ConfidentialAccountsService {
public async applyAllIncomingAssetBalances(
confidentialAccount: string,
base: TransactionBaseDto
): ServiceReturn<ConfidentialAccount> {
): ServiceReturn<IncomingConfidentialAssetBalance[]> {
const { options } = extractTxOptions(base);
const applyIncomingBalances =
this.polymeshService.polymeshApi.confidentialAccounts.applyIncomingBalances;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* istanbul ignore file */

import { ApiProperty } from '@nestjs/swagger';

import { ConfidentialAssetBalanceModel } from '~/confidential-accounts/models/confidential-asset-balance.model';

export class AppliedConfidentialAssetBalanceModel extends ConfidentialAssetBalanceModel {
@ApiProperty({
description: 'Encrypted amount of confidential Asset which was deposited',
type: 'string',
example:
'0x289ebc384a263acd5820e03988dd17a3cd49ee57d572f4131e116b6bf4c70a1594447bb5d1e2d9cc62f083d8552dd90ec09b23a519b361e458d7fe1e48882261',
})
readonly amount: string;

constructor(model: AppliedConfidentialAssetBalanceModel) {
const { amount, ...rest } = model;
super(rest);
Object.assign(this, { amount });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* istanbul ignore file */

import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';

import { AppliedConfidentialAssetBalanceModel } from '~/confidential-accounts/models/applied-confidential-asset-balance.model';
import { TransactionQueueModel } from '~/polymesh-rest-api/src/common/models/transaction-queue.model';

export class AppliedConfidentialAssetBalancesModel extends TransactionQueueModel {
@ApiProperty({
type: AppliedConfidentialAssetBalanceModel,
isArray: true,
description: 'List of all applied asset balances',
})
@Type(() => AppliedConfidentialAssetBalanceModel)
readonly appliedAssetBalances: AppliedConfidentialAssetBalanceModel[];

constructor(model: AppliedConfidentialAssetBalancesModel) {
const { transactions, details, ...rest } = model;
super({ transactions, details });

Object.assign(this, rest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ export class ConfidentialAssetDetailsModel {
@ApiProperty({
description: 'Auditor Confidential Accounts configured for the Confidential Asset',
type: ConfidentialAccountModel,
isArray: true,
})
@Type(() => ConfidentialAccountModel)
readonly auditors: ConfidentialAccountModel[];

@ApiPropertyOptional({
description: 'Mediator Identities configured for the Confidential Asset',
type: IdentityModel,
isArray: true,
})
@Type(() => IdentityModel)
readonly mediators?: IdentityModel[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ describe('ConfidentialTransactionsService', () => {

await expect(() => service.findOne(id)).rejects.toThrowError();

expect(handleSdkErrorSpy).toHaveBeenCalledWith(mockError);
expect(handleSdkErrorSpy).toHaveBeenCalledWith(mockError, {
id: id.toString(),
resource: 'Confidential Transaction',
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class ConfidentialTransactionsService {
return await this.polymeshService.polymeshApi.confidentialSettlements
.getTransaction({ id })
.catch(error => {
throw handleSdkError(error);
throw handleSdkError(error, { id: id.toString(), resource: 'Confidential Transaction' });
});
}

Expand Down
10 changes: 10 additions & 0 deletions src/transactions/transactions.util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,14 @@ describe('handleSdkError', () => {
expect(error).toBeInstanceOf(expectedError);
});
});
it('should return AppNotFoundError with resource specific info', () => {
const inputError = new PolymeshError({ code: ErrorCode.DataUnavailable, message: '' });
when(mockIsPolymeshError).calledWith(inputError).mockReturnValue(true);
const error = handleSdkError(inputError, { id: '1', resource: 'Example Resource' });

expect(error).toBeInstanceOf(AppNotFoundError);
expect(error.message).toEqual(
'Not Found: Example Resource was not found: with identifier: "1"'
);
});
});
5 changes: 4 additions & 1 deletion src/transactions/transactions.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export async function processTransaction<
}
}

export function handleSdkError(err: unknown): AppError {
export function handleSdkError(err: unknown, args?: { id: string; resource: string }): AppError {
if (isAppError(err)) {
// don't transform App level errors
return err;
Expand All @@ -192,6 +192,9 @@ export function handleSdkError(err: unknown): AppError {
case ErrorCode.LimitExceeded:
return new AppUnprocessableError(message);
case ErrorCode.DataUnavailable:
if (args) {
return new AppNotFoundError(args.id, args.resource);
}
return new AppNotFoundError(message, '');
case ErrorCode.NotAuthorized:
return new AppUnauthorizedError(message);
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2127,10 +2127,10 @@
dependencies:
"@polymeshassociation/signing-manager-types" "^3.2.0"

"@polymeshassociation/polymesh-private-sdk@1.2.0-alpha.2":
version "1.2.0-alpha.2"
resolved "https://registry.yarnpkg.com/@polymeshassociation/polymesh-private-sdk/-/polymesh-private-sdk-1.2.0-alpha.2.tgz#359f7dfd116f22aa3ae233554acfe24408b3310a"
integrity sha512-o/idh9HCd/J8OnWStj81CdXlDl6vfwVaQQ/qZNRi7ByKGG5Ax0+yAGfP7oFhywOFO1jThg5+MNu19wU/G4QECw==
"@polymeshassociation/polymesh-private-sdk@2.0.0-alpha.1":
version "2.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/@polymeshassociation/polymesh-private-sdk/-/polymesh-private-sdk-2.0.0-alpha.1.tgz#2ab01950798b1c7fff767e81a65f959fbe3bc257"
integrity sha512-0x7jsd6lWHibREneLFy9VCcl5bqHaIwxvQyowz/HSEEQwnBFG2ZEcSb2fkqra3ld46+1YSfWuDBtl1LT1FP7AA==
dependencies:
"@apollo/client" "^3.8.1"
"@noble/curves" "^1.4.0"
Expand Down
Loading