Skip to content

Commit

Permalink
fix: basket validations (#2451)
Browse files Browse the repository at this point in the history
  • Loading branch information
sokolova-an authored Oct 11, 2024
1 parent ddba86b commit 5fcc40f
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 6 deletions.
16 changes: 14 additions & 2 deletions src/renderer/features/governance/model/vote/voteValidateModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createEffect, createEvent, sample } from 'effector';
import { type Asset, type Balance, type Chain, type ID, type Transaction } from '@shared/core';
import { toAccountId, transferableAmount } from '@shared/lib/utils';
import { balanceModel, balanceUtils } from '@entities/balance';
import { votingService } from '@entities/governance';
import { governanceService, referendumService, votingService } from '@entities/governance';
import { networkModel } from '@entities/network';
import { transactionService } from '@entities/transaction';
import {
Expand All @@ -32,10 +32,12 @@ const validateFx = createEffect(
async ({ id, api, chain, asset, transaction, balances, signerOptions }: ValidateParams) => {
const accountId = toAccountId(transaction.address);
const fee = await transactionService.getTransactionFee(transaction, api, signerOptions);
const referendum = await governanceService.getReferendums(api, [transaction.args.referendum]);
const isOngoing = referendumService.isOngoing(referendum[0]);

const shardBalance = balanceUtils.getBalance(balances, accountId, chain.chainId, asset.assetId.toString());

const rules: Validation<BN, { shards: unknown[] }, AmountFeeStore>[] = [
const rules: Validation<BN, { shards: unknown[] }>[] = [
{
name: 'insufficientBalanceForFee',
errorText: 'transfer.notEnoughBalanceForFeeError',
Expand All @@ -57,6 +59,16 @@ const validateFx = createEffect(
});
},
},
{
name: 'timeoutReferendum',
errorText: 'governance.referendums.vote.timeoutReferendumError',
value: BN_ZERO,
form: { shards: [{ accountId }] },
source: isOngoing,
validator: (_v, _f, isOngoing: boolean) => {
return isOngoing;
},
},
];

return { id, result: validationUtils.applyValidationRules(rules) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { unstakeValidateModel } from './model/unstake-validate-model';
export { withdrawValidateModel } from './model/withdraw-validate-model';
export { delegateValidateModel } from './model/delegate-validate-model';
export { revokeDelegationValidateModel } from './model/revoke-delegation-validate-model';
export { removeVoteValidateModel } from './model/remove-vote-validate-model';

export { TransferRules } from './lib/transfer-rules';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { type ApiPromise } from '@polkadot/api';
import { type SignerOptions } from '@polkadot/api/submittable/types';
import { BN, BN_ZERO } from '@polkadot/util';
import { createEffect, createEvent, sample } from 'effector';

import { convictionVotingPallet } from '@/shared/pallet/convictionVoting';
import { type Asset, type Balance, type Chain, type ID, type Transaction } from '@shared/core';
import { toAccountId, transferableAmount } from '@shared/lib/utils';
import { balanceModel, balanceUtils } from '@entities/balance';
import { votingService } from '@entities/governance';
import { networkModel } from '@entities/network';
import { transactionService } from '@entities/transaction';
import {
type AmountFeeStore,
type Validation,
type ValidationStartedParams,
validationUtils,
} from '@/features/operations/OperationsValidation';

const validationStarted = createEvent<ValidationStartedParams>();

type ValidateParams = {
id: ID;
api: ApiPromise;
chain: Chain;
asset: Asset;
transaction: Transaction;
balances: Balance[];
signerOptions?: Partial<SignerOptions>;
};

const validateFx = createEffect(
async ({ id, api, chain, asset, transaction, balances, signerOptions }: ValidateParams) => {
const accountId = toAccountId(transaction.address);
const fee = await transactionService.getTransactionFee(transaction, api, signerOptions);

const votes = await convictionVotingPallet.storage.votingFor(api, [[transaction.address, transaction.args.track]]);
const voting = votes.find((vote) => vote.type === 'Casting');
const isVoteExist = voting?.data.votes.find((vote) => vote.referendum === +transaction.args.referendum);

const shardBalance = balanceUtils.getBalance(balances, accountId, chain.chainId, asset.assetId.toString());

const rules: Validation<BN, { shards: unknown[] }>[] = [
{
name: 'insufficientBalanceForFee',
errorText: 'transfer.notEnoughBalanceForFeeError',
value: BN_ZERO,
form: { shards: [{ accountId }] },
source: {
isMultisig: false,
network: { chain, asset },
feeData: { fee },
accountsBalances: [transferableAmount(shardBalance)],
},
validator: (_v, form, { feeData, isMultisig, accountsBalances }: AmountFeeStore) => {
if (isMultisig) return true;

const feeBN = new BN(feeData.fee);

return form.shards.every((_, index: number) => {
return feeBN.lte(new BN(accountsBalances[index]));
});
},
},
{
name: 'noVoteForReferendum',
errorText: 'governance.referendums.vote.noVoteForReferendum',
value: BN_ZERO,
form: { shards: [{ accountId }] },
source: isVoteExist,
validator: (_v, _f, isVoteExist: boolean) => {
return isVoteExist;
},
},
];

return { id, result: validationUtils.applyValidationRules(rules) };
},
);

sample({
clock: validationStarted,
source: {
chains: networkModel.$chains,
apis: networkModel.$apis,
balances: balanceModel.$balances,
},
filter: ({ apis }, { transaction }) => transaction.chainId in apis,
fn: ({ apis, chains, balances }, { id, transaction, signerOptions }) => {
const chain = chains[transaction.chainId];
const api = apis[transaction.chainId];
const asset = votingService.getVotingAsset(chain);

return {
id,
api,
transaction,
chain,
asset: asset!,
balances,
signerOptions,
};
},
target: validateFx,
});

export const removeVoteValidateModel = {
events: {
validationStarted,
},
output: {
txValidated: validateFx.doneData,
},
};
5 changes: 3 additions & 2 deletions src/renderer/pages/Basket/model/basket-page-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
payeeValidateModel,
removeProxyValidateModel,
removePureProxiedValidateModel,
removeVoteValidateModel,
restakeValidateModel,
revokeDelegationValidateModel,
transferValidateModel,
Expand Down Expand Up @@ -153,8 +154,7 @@ const validateFx = createEffect(({ transactions, feeMap }: ValidateParams) => {
[TransactionType.EDIT_DELEGATION]: delegateValidateModel.events.validationStarted,
[TransactionType.VOTE]: voteValidateModel.events.validationStarted,
[TransactionType.REVOTE]: voteValidateModel.events.validationStarted,
// TODO: add separate validation for remove vote
[TransactionType.REMOVE_VOTE]: voteValidateModel.events.validationStarted,
[TransactionType.REMOVE_VOTE]: removeVoteValidateModel.events.validationStarted,
};

if (coreTx.type in TransactionValidatorsRecord) {
Expand Down Expand Up @@ -193,6 +193,7 @@ const txValidated = [
delegateValidateModel.output.txValidated,
revokeDelegationValidateModel.output.txValidated,
voteValidateModel.output.txValidated,
removeVoteValidateModel.output.txValidated,
];

sample({
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/shared/api/translation/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,9 @@
"lockingPeriod": "Locking period",
"networkFee": "Network fee",
"tracks": "Tracks"
}
},
"noVoteForReferendum": "There're no existing votes for this referendum",
"timeoutReferendumError": "This referendum has already passed"
},
"voteHistory": {
"listColumnAccount": "Account",
Expand Down
6 changes: 5 additions & 1 deletion src/renderer/shared/pallet/referenda/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ export const storage = {
),
);

const schemaWithIds = pjsSchema
.vec(pjsSchema.optional(referendaReferendumInfoConvictionVotingTally))
.transform(items => items.map((item, index) => ({ info: item, id: ids![index] })));

if (ids) {
return substrateRpcPool.call(() => getQuery(type, api, 'referendumInfoFor').entries(ids)).then(schema.parse);
return substrateRpcPool.call(() => getQuery(type, api, 'referendumInfoFor').multi(ids)).then(schemaWithIds.parse);
} else {
return substrateRpcPool.call(() => getQuery(type, api, 'referendumInfoFor').entries()).then(schema.parse);
}
Expand Down

0 comments on commit 5fcc40f

Please sign in to comment.