Skip to content

Commit

Permalink
feat: implement dryRunCall option (#433)
Browse files Browse the repository at this point in the history
  • Loading branch information
marshacb authored Sep 24, 2024
1 parent bb1f4d9 commit 9ea6e20
Show file tree
Hide file tree
Showing 17 changed files with 720 additions and 11 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ interface TransferArgsOpts<T extends Format> {
* Defaults to `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`
*/
customXcmOnDest?: string;
/**
* Optionally allows for dry running the constructed tx in order get the estimated fees and execution result.
*/
dryRunCall?: boolean;

/**
* Optional assetId that will be used to pay for fees. Used with the `dryRunCall` option to determine fees in the specified asset.
*/
xcmFeeAsset?: string;
}
```

Expand Down
52 changes: 52 additions & 0 deletions examples/dryRunCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* When importing from @substrate/asset-transfer-api it would look like the following
*
* import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api'
*/
import { AssetTransferApi, constructApiPromise } from '../src';
import { TxResult } from '../src/types';
import { GREEN, PURPLE, RESET } from './colors';

/**
* In this example we are creating a `polkadotXcm` pallet `transferAssets` call to send 1 WND (asset with location `{"parents":"1","interior":{"Here":""}}`)
* from a Westend Asset Hub (System Parachain) account
* to a Westend BridheHub account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that
* the tx will allow unlimited weight to be used for fees. The `dryRunCall` option is set to true, which allows for the transaction to be dry run after being constructed.
* The `XcmFeeAsset` is set to `wnd` which will be the asset used to estimate fees during the dry run of the extrinsic.
* The fee for each XCM and the overall execution result will be returned with the TxResult info.
*
* NOTE: When dry running a call, the `sendersAddr` and `xcmFeeAsset` fields must be provided.
*/
const main = async () => {
const { api, specName } = await constructApiPromise('wss://westend-asset-hub-rpc.polkadot.io');
const xcmVersion = 4;
const assetApi = new AssetTransferApi(api, specName, xcmVersion);
let callInfo: TxResult<'call'>;
try {
callInfo = await assetApi.createTransferTransaction(
'1002',
'5HBuLJz9LdkUNseUEL6DLeVkx2bqEi6pQr8Ea7fS4bzx7i7E',
['wnd'],
['10000000000'],
{
format: 'call',
dryRunCall: true,
xcmFeeAsset: 'wnd',
sendersAddr: '5EJWF8s4CEoRU8nDhHBYTT6QGFGqMXTmdQdaQJVEFNrG9sKy',
xcmVersion: xcmVersion,
},
);

console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`);
} catch (e) {
console.error(e);
throw Error(e as string);
}

const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call');
console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`);
};

main()
.catch((err) => console.error(err))
.finally(() => process.exit());
79 changes: 79 additions & 0 deletions src/AssetTransferApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { adjustedMockRelayApiNoLimitedReserveTransferAssets } from './testHelper
import { adjustedMockRelayApi } from './testHelpers/adjustedMockRelayApiV9420';
import { adjustedMockSystemApi } from './testHelpers/adjustedMockSystemApiV1004000';
import { adjustedMockSystemApiV1016000 } from './testHelpers/adjustedMockSystemApiV1016000';
import { mockDryRunCallResult } from './testHelpers/mockDryRunCallResult';
import { mockSystemApi } from './testHelpers/mockSystemApi';
import { mockWeightInfo } from './testHelpers/mockWeightInfo';
import { AssetCallType, Direction, ResolvedCallInfo, UnsignedTransaction, XcmBaseArgs, XcmDirection } from './types';
Expand Down Expand Up @@ -490,6 +491,84 @@ describe('AssetTransferAPI', () => {
});
});

describe('getXcmWeightToFee', () => {
it('Should correctly return the xcm fee for a valid result', () => {
const xcmWeightToFeeAssetResult = westmintAssetsApi.api.registry.createType('Result<u128, XcmPaymentApiError>', {
ok: 100000000000,
});

expect(
westmintAssetsApi['getXcmWeightToFee'](xcmWeightToFeeAssetResult, {
V4: { parents: 1, interior: { Here: '' } },
}),
).toEqual({ xcmFee: '100000000000' });
});
it('Should correctly throw an error when given an error result', () => {
const xcmWeightToFeeAssetResult = westmintAssetsApi.api.registry.createType('Result<u128, XcmPaymentApiError>', {
err: 'AssetNotFound',
});
const assetLocation = { V4: { parents: 1, interior: { Here: '' } } };

const err = () =>
westmintAssetsApi['getXcmWeightToFee'](xcmWeightToFeeAssetResult, {
V4: { parents: 1, interior: { Here: '' } },
});
expect(err).toThrow(`XcmFeeAsset Error: AssetNotFound - asset: ${JSON.stringify(assetLocation)}`);
});
});
describe('dryRunCall', () => {
const sendersAddress = '5HBuLJz9LdkUNseUEL6DLeVkx2bqEi6pQr8Ea7fS4bzx7i7E';

it('Should correctly execute a dry run for a submittable extrinsic', async () => {
const executionResult = await westmintAssetsApi.dryRunCall(sendersAddress, mockSubmittableExt, 'submittable');

expect(executionResult?.asOk.executionResult.asOk.paysFee.toString()).toEqual(
mockDryRunCallResult.Ok.executionResult.Ok.paysFee,
);
});

it('Should correctly execute a dry run for a payload extrinsic', async () => {
const payloadTexResult = await westmintAssetsApi['constructFormat'](
mockSubmittableExt,
Direction.SystemToPara,
4,
'transferAssets',
'0',
'asset-hub-westend',
{ format: 'payload' },
);

const executionResult = await westmintAssetsApi.dryRunCall(sendersAddress, payloadTexResult.tx, 'payload');
expect(executionResult?.asOk.executionResult.asOk.paysFee.toString()).toEqual(
mockDryRunCallResult.Ok.executionResult.Ok.paysFee,
);
});

it('Should correctly execute a dry run for a call', async () => {
const callTxResult = await westmintAssetsApi['constructFormat'](
mockSubmittableExt,
Direction.SystemToPara,
4,
'transferAssets',
'0',
'asset-hub-westend',
{
format: 'call',
dryRunCall: true,
xcmFeeAsset: 'wnd',
sendersAddr: 'FBeL7DanUDs5SZrxZY1CizMaPgG9vZgJgvr52C2dg81SsF1',
},
);

expect(callTxResult.localXcmFees![1]).toEqual({ xcmFee: '3500000000000000' });

const executionResult = await westmintAssetsApi.dryRunCall(sendersAddress, callTxResult.tx, 'call');
expect(executionResult?.asOk.executionResult.asOk.paysFee.toString()).toEqual(
mockDryRunCallResult.Ok.executionResult.Ok.paysFee,
);
});
});

describe('decodeExtrinsic', () => {
describe('ParaToPara', () => {
it('Should decode a tx call extrinsic given its hash for ParaToPara', async () => {
Expand Down
Loading

0 comments on commit 9ea6e20

Please sign in to comment.