Skip to content

Commit

Permalink
Peter/feat dry running (#1499)
Browse files Browse the repository at this point in the history
* chore: update monetary to latest 0.7.3

* feat(transaction): dry-run transaction before submission and revert execution if dry-running fails

* test: mock submittable extrinsic

* refactor: rename to dryRun and document functionality

* refactor: move submission code to separate folder
  • Loading branch information
peterslany authored Aug 2, 2023
1 parent 12b9271 commit f563370
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/hooks/transaction/hooks/use-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useSubstrate } from '@/lib/substrate';
import { useGetLiquidityPools } from '../../api/amm/use-get-liquidity-pools';
import { useGetBalances } from '../../api/tokens/use-get-balances';
import { getExtrinsic, getStatus } from '../extrinsics';
import { submitTransaction } from '../submission/submit';
import { Transaction, TransactionActions } from '../types';
import {
TransactionResult,
Expand All @@ -21,7 +22,6 @@ import {
} from '../types/hook';
import { wrapWithTxFeeSwap } from '../utils/fee';
import { getActionData, getAmountWithFeeDeducted } from '../utils/params';
import { submitTransaction } from '../utils/submit';
import { FeeEstimateResult, useFeeEstimate } from './use-fee-estimate';
import { useTransactionNotifications } from './use-transaction-notifications';

Expand Down
36 changes: 36 additions & 0 deletions src/hooks/transaction/submission/dry-run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { SubmittableExtrinsic } from '@polkadot/api/types';

import { getErrorMessage } from './error';

/**
* Dry-run signed submittable extrinsic if dry-running is enabled on RPC node.
*
* @throws If extrinsic execution failed during dry running.
* @param signedExtrinsic Extrinsic to be dry run.
* @returns {SubmittableExtrinsic} Dry-ran extrinsic.
*/

const dryRun = async (signedExtrinsic: SubmittableExtrinsic<'promise'>): Promise<SubmittableExtrinsic<'promise'>> => {
// Dry-run if enabled on RPC node.
// Source: Polkadot.js, https://github.com/polkadot-js/api/blob/319535a1e938e89522ff18ef2d1cef66a5af597c/packages/api/src/submittable/createClass.ts#L110
if (signedExtrinsic.hasDryRun) {
const dryRunResult = await window.bridge.api.rpc.system.dryRun(signedExtrinsic.toHex());

// If dry-running fails, code execution throws and extrinsic is not submitted.
if (dryRunResult.isErr) {
const error = dryRunResult.asErr;
const errMessage = error.toString();
throw new Error(errMessage);
}

// Handle dry-run result nested error.
if (dryRunResult.isOk && dryRunResult.asOk.isErr) {
const error = dryRunResult.asOk.asErr;
const errMessage = getErrorMessage(window.bridge.api, error);
throw new Error(errMessage);
}
}
return signedExtrinsic;
};

export { dryRun };
23 changes: 23 additions & 0 deletions src/hooks/transaction/submission/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ApiPromise } from '@polkadot/api';
import { DispatchError } from '@polkadot/types/interfaces';

const getErrorMessage = (api: ApiPromise, dispatchError: DispatchError): string => {
const { isModule, asModule, isBadOrigin } = dispatchError;

// Runtime error in one of the parachain modules
if (isModule) {
// for module errors, we have the section indexed, lookup
const decoded = api.registry.findMetaError(asModule);
const { docs, name, section } = decoded;
return `The error code is ${section}.${name}. ${docs.join(' ')}.`;
}

// Bad origin
if (isBadOrigin) {
return `The error is caused by using an incorrect account. The error code is BadOrigin ${dispatchError}.`;
}

return `The error is ${dispatchError}.`;
};

export { getErrorMessage };
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ExtrinsicData } from '@interlay/interbtc-api';
import { ApiPromise } from '@polkadot/api';
import { AddressOrPair, SubmittableExtrinsic } from '@polkadot/api/types';
import { DispatchError } from '@polkadot/types/interfaces';
import { ExtrinsicStatus } from '@polkadot/types/interfaces/author';
import { ISubmittableResult } from '@polkadot/types/types';

import { TransactionResult } from '../hooks/use-transaction';
import { TransactionEvents } from '../types';
import { dryRun } from './dry-run';
import { getErrorMessage } from './error';

type HandleTransactionResult = { result: ISubmittableResult; unsubscribe: () => void };

Expand All @@ -23,9 +24,12 @@ const handleTransaction = async (

return new Promise<HandleTransactionResult>((resolve, reject) => {
let unsubscribe: () => void;

(extrinsicData.extrinsic as SubmittableExtrinsic<'promise'>)
.signAndSend(account, { nonce: -1 }, callback)
// Extrinsic is signed at first and then we use the same signed extrinsic
// for dry-running and submission.
.signAsync(account, { nonce: -1 })
.then(dryRun)
.then((signedExtrinsic) => signedExtrinsic.send(callback))
.then((unsub) => (unsubscribe = unsub))
.catch((error) => reject(error));

Expand All @@ -48,25 +52,6 @@ const handleTransaction = async (
});
};

const getErrorMessage = (api: ApiPromise, dispatchError: DispatchError) => {
const { isModule, asModule, isBadOrigin } = dispatchError;

// Runtime error in one of the parachain modules
if (isModule) {
// for module errors, we have the section indexed, lookup
const decoded = api.registry.findMetaError(asModule);
const { docs, name, section } = decoded;
return `The error code is ${section}.${name}. ${docs.join(' ')}.`;
}

// Bad origin
if (isBadOrigin) {
return `The error is caused by using an incorrect account. The error code is BadOrigin ${dispatchError}.`;
}

return `The error is ${dispatchError}.`;
};

/**
* Handles transaction submittion and error
* @param {ApiPromise} api polkadot api wrapper
Expand Down
17 changes: 10 additions & 7 deletions src/test/mocks/@interlay/interbtc-api/extrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { SubmittableExtrinsic } from '@polkadot/api/types';
import { ISubmittableResult } from '@polkadot/types/types';

const EXTRINSIC: SubmittableExtrinsic<'promise', ISubmittableResult> = ({
signAndSend: jest.fn().mockImplementation(async (_a, _b, cb) => {
return new Promise((resolve) => {
resolve(jest.fn());
signAsync: jest.fn().mockResolvedValue({
send: jest.fn().mockImplementation(async (callback) => {
return new Promise((resolve) => {
resolve(jest.fn());

setTimeout(() => {
cb({ status: { isReady: true, isInBlock: true, isFinalized: true, type: 'Finalized' } });
}, 1);
});
setTimeout(() => {
callback({ status: { isReady: true, isInBlock: true, isFinalized: true, type: 'Finalized' } });
}, 1);
});
}),
hadDryRun: false
})
} as unknown) as SubmittableExtrinsic<'promise', ISubmittableResult>;

Expand Down

2 comments on commit f563370

@vercel
Copy link

@vercel vercel bot commented on f563370 Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on f563370 Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.