Ensure assetId is handled correctly when addSignature is used (#6083)

* Ensure assetId is handled correctly when addSignature is used

* Adjust test to mirror real failure

* test empty input on GenericExtrinsicPayload
This commit is contained in:
Tarik Gul
2025-01-29 22:04:23 +02:00
committed by GitHub
parent ad6f3d4531
commit c129dda5ce
4 changed files with 62 additions and 39 deletions
@@ -95,4 +95,22 @@ describe('ExtrinsicPayload', (): void => {
'0x940600ffd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c0703d148e25901007b000000dcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025bde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7'
);
});
it('has a sane inspect of an empty value', (): void => {
const reg = new TypeRegistry();
reg.setSignedExtensions(fallbackExtensions.concat(['ChargeAssetTxPayment']));
expect(new ExtrinsicPayload(reg, undefined).inspect()).toEqual({
inner: [
{ name: 'method', outer: [new Uint8Array()] },
{ inner: undefined, name: 'era', outer: [new Uint8Array([0]), new Uint8Array([0])] },
{ name: 'nonce', outer: [new Uint8Array([0])] },
{ name: 'tip', outer: [new Uint8Array([0])] },
{ name: 'assetId', outer: [new Uint8Array([0])] },
{ name: 'specVersion', outer: [new Uint8Array([0, 0, 0, 0])] },
{ name: 'genesisHash', outer: [new Uint8Array(32)] },
{ name: 'blockHash', outer: [new Uint8Array(32)] }
]
});
});
});
@@ -39,6 +39,33 @@ const PREAMBLES = {
general: 'ExtrinsicPayloadV5'
};
/**
* HACK: In order to change the assetId from `number | object` to HexString (While maintaining the true type ie Option<TAssetConversion>),
* to allow for easier generalization of the SignerPayloadJSON interface the below check is necessary. The ExtrinsicPayloadV4 class does not like
* a value passed in as an Option, and can't decode it properly. Therefore, we ensure to convert the following below, and then pass the option as a unwrapped
* JSON value.
*
* ref: https://github.com/polkadot-js/api/pull/5968
* ref: https://github.com/polkadot-js/api/pull/5967
*/
export function decodeAssetId (registry: Registry, payload?: ExtrinsicPayloadValue | Uint8Array | HexString) {
const maybeAsset = (payload as ExtrinsicPayloadValue)?.assetId;
if (maybeAsset && isHex(maybeAsset)) {
const assetId = registry.createType('TAssetConversion', hexToU8a(maybeAsset));
// we only want to adjust the payload if the hex passed has the option
if (maybeAsset === '0x00' || maybeAsset === '0x01' + assetId.toHex().slice(2)) {
return {
...(payload as ExtrinsicPayloadValue),
assetId: assetId.toJSON()
};
}
}
return payload;
}
/** @internal */
function decodeExtrinsicPayload (registry: Registry, value?: GenericExtrinsicPayload | ExtrinsicPayloadValue | Uint8Array | string, version = LATEST_EXTRINSIC_VERSION, preamble: Preamble = DEFAULT_PREAMBLE): ExtrinsicPayloadVx {
if (value instanceof GenericExtrinsicPayload) {
@@ -46,32 +73,9 @@ function decodeExtrinsicPayload (registry: Registry, value?: GenericExtrinsicPay
}
const extVersion = version === 5 ? PREAMBLES[preamble] : VERSIONS[version] || VERSIONS[0];
const payload = decodeAssetId(registry, value as ExtrinsicPayloadValue);
/**
* HACK: In order to change the assetId from `number | object` to HexString (While maintaining the true type ie Option<TAssetConversion>),
* to allow for easier generalization of the SignerPayloadJSON interface the below check is necessary. The ExtrinsicPayloadV4 class does not like
* a value passed in as an Option, and can't decode it properly. Therefore, we ensure to convert the following below, and then pass the option as a unwrapped
* JSON value.
*
* ref: https://github.com/polkadot-js/api/pull/5968
* ref: https://github.com/polkadot-js/api/pull/5967
*/
if (value && (value as ExtrinsicPayloadValue).assetId && isHex((value as ExtrinsicPayloadValue).assetId)) {
const assetId = registry.createType('TAssetConversion', hexToU8a((value as ExtrinsicPayloadValue).assetId));
// we only want to adjust the payload if the hex passed has the option
if ((value as ExtrinsicPayloadValue).assetId === '0x00' ||
(value as ExtrinsicPayloadValue).assetId === '0x01' + assetId.toHex().slice(2)) {
const adjustedPayload = {
...(value as ExtrinsicPayloadValue),
assetId: assetId.toJSON()
};
return registry.createTypeUnsafe(extVersion, [adjustedPayload, { version }]);
}
}
return registry.createTypeUnsafe(extVersion, [value, { version }]);
return registry.createTypeUnsafe(extVersion, [payload, { version }]);
}
/**
@@ -38,24 +38,24 @@ describe('ExtrinsicPayload', (): void => {
});
});
it('correctly decodes assetId', () => {
const TEST_WITH_ASSET = {
address: '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE',
assetId: '0x0002043205011f' as `0x${string}`,
blockHash: '0xde8f69eeb5e065e18c6950ff708d7e551f68dc9bf59a07c52367c0280f805ec7',
era: '0x0703',
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
method: '0x0600ffd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9e56c',
nonce: '0x00001234',
specVersion: 123,
tip: '0x00000000000000000000000000005678',
transactionVersion: '0x00'
it('Correctly decodes assetId', () => {
const TEST_VALUE = {
address: 'J97drEQy6sYPXf2D1uj1hJfeHsxjvwr4tVGKs9o8VDSht8r',
assetId: '0x010002043205011f' as `0x${string}`,
blockHash: '0x28a464e6b40fccec3b9e7989db97d2627d3653c644a3c801f8239910eaaa58a8',
era: '0x4401',
genesisHash: '0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a',
method: '0x0a0300d27001b334c34489c67b81dfbbdc86eba5b433163bd08226d89b081914e9aa490284d717',
nonce: '0x0000000a',
specVersion: '0x000f4dfc',
tip: '0x00000000000000000000000000000000',
transactionVersion: '0x0000000f'
};
const reg = new TypeRegistry();
reg.setSignedExtensions(fallbackExtensions.concat(['ChargeAssetTxPayment']));
const ext = new ExtrinsicPayload(reg, TEST_WITH_ASSET);
const ext = new ExtrinsicPayload(reg, TEST_VALUE);
expect(ext.assetId.toJSON()).toEqual({
interior: {
@@ -13,6 +13,7 @@ import type { ExtrinsicPayloadValue, ICompact, IKeyringPair, INumber, IOption }
import { Enum, Struct } from '@polkadot/types-codec';
import { objectSpread } from '@polkadot/util';
import { decodeAssetId } from '../ExtrinsicPayload.js';
import { sign } from '../util.js';
/**
@@ -29,7 +30,7 @@ export class GenericExtrinsicPayloadV4 extends Struct {
{ method: 'Bytes' },
registry.getSignedExtensionTypes(),
registry.getSignedExtensionExtra()
), value);
), decodeAssetId(registry, value));
// Do detection for the type of extrinsic, in the case of MultiSignature
// this is an enum, in the case of AnySignature, this is a Hash only