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

Problem using ChargeAssetTxPayment SignedExtension #5710

Closed
jsidorenko opened this issue Aug 24, 2023 · 4 comments · Fixed by #5716
Closed

Problem using ChargeAssetTxPayment SignedExtension #5710

jsidorenko opened this issue Aug 24, 2023 · 4 comments · Fixed by #5716

Comments

@jsidorenko
Copy link
Contributor

I was trying to check the ChargeAssetTxPayment SignedExtension and noticed an issue with the data encoding/decoding, which prevents us from using such an extension.

In the beginning, I've noticed the tx goes through and works well when I use the KeyPair and submit it in this way:

const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
const charlie = keyring.addFromUri('//Charlie');
await api.tx.balances.transfer(charlie.address, native(100)).signAsync(alice, { assetId: 1 });

Next, I tried to use a similar approach and inject a signer (could be from web extension or simulated like in the code below):

const signer = new SingleAccountSigner(registry, alice);
await api.tx.balances.transfer(charlie.address, native(100)).signAsync(alice.address, { signer, assetId: 1 });

And that tx fails!
By logging the received data in the node, I discovered that instead of assetId=1 the runtime receives assetId=16777216.


After days of debugging, I was able to reconstruct the whole data-flow:

  1. createClass.#signViaSigner receives the correct assetId and then creates a payload in such a way:
    const payload = this.registry.createTypeUnsafe<SignerPayload>('SignerPayload'....
  2. it asks the signer to sign the payload:
    if (isFunction(signer.signPayload)) { result = await signer.signPayload(payload.toPayload()); }
  3. then stores the payload internally:
    super.addSignature(address, result.signature, payload.toPayload());

if we log the assetId (console.log(payload.toPayload().assetId);) it will print 0x01000000.
That's because it converts all the values to hex here and api.createType('Option<u32>', 1).toHex() returns 0x01000000.

  1. Now, having the assetId='0x01000000' signer tries to construct the ExtrinsicPayload:
    const extrinsicPayload = this.#registry.createType('ExtrinsicPayload', payload, { version: payload.version });
    Once I logged the asset id from extrinsicPayload I received 16777216
    console.log(extrinsicPayload.toHuman().assetId.toString());
    I assume this is because api.createType('Option<u32>', '0x01000000').toString() returns 16777216

A quick way to reproduce (e.g. put the code below into this spec file):

    const registry = new TypeRegistry();
    const keyring = createTestKeyring({ type: 'ed25519' });
    const aliceEd = keyring.addPair(
      createPair({ toSS58: keyring.encodeAddress, type: 'ed25519' }, {
        publicKey: hexToU8a('0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee'),
        secretKey: hexToU8a('0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a7690911588dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee')
      })
    );
    const signer = new SingleAccountSigner(registry, aliceEd);
    const api = await ApiPromise.create({ provider, registry, signer, throwOnConnect: true });

    const receiver = keyring.getPair('0xe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e').address;
    const sendValue = 321564789876512345n;
    const transfer1 = await api.tx.balances.transferAllowDeath(receiver, sendValue).signAsync(aliceEd.address, { assetId: 1 });
    const transfer2 = await api.tx.balances.transferAllowDeath(receiver, sendValue).signAsync(aliceEd, { assetId: 1 });

    console.log(transfer1.inner.signature.assetId.toString()); // 16777216
    console.log(transfer2.inner.signature.assetId.toString()); // 1
@jsidorenko
Copy link
Contributor Author

jsidorenko commented Aug 24, 2023

TLDR:

api.createType('Option<AssetId>', 1).toHex() returns ‘0x01000000’.
api.createType('Option<AssetId>', '0x01000000').toString() returns 16777216

@jsidorenko
Copy link
Contributor Author

jsidorenko commented Aug 24, 2023

As a possible solution we can apply a fix here:

      if (!isOption || value.isSome) {
        result[key] = isOption ? value.unwrap().toHex() : value.toHex();
      }

By unwrapping the optional value we'll get the proper value we need:

api.createType('Option<AssetId>', 1).toHex() returns ‘0x01000000’
api.createType('Option<AssetId>', 1).unwrap().toHex() returns ‘0x00000001’

@Robiquet
Copy link

Great investigation! We are seeing the same issue at Zeitgeist, we have added ChargeAssetTxPayment to our runtime but are unable to get it to work on our frontend with assetIds other than 0

@polkadot-js-bot
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue if you think you have a related problem or query.

@polkadot-js polkadot-js locked as resolved and limited conversation to collaborators Sep 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants