From b515dcea73c9c97ddd56277e425825d0888b6fda Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Mon, 30 Dec 2024 16:53:26 -0500 Subject: [PATCH 1/3] fix: make e2e tests for passkey more rigorous in their success checks --- e2e/passkey/passkeyProxyV2.ethereum.test.ts | 105 ++++++++++++++++---- e2e/passkey/passkeyProxyV2.test.ts | 53 +++++++--- e2e/scaffolding/extrinsicHelpers.ts | 16 ++- 3 files changed, 141 insertions(+), 33 deletions(-) diff --git a/e2e/passkey/passkeyProxyV2.ethereum.test.ts b/e2e/passkey/passkeyProxyV2.ethereum.test.ts index d5ce1b3730..1f06208d68 100644 --- a/e2e/passkey/passkeyProxyV2.ethereum.test.ts +++ b/e2e/passkey/passkeyProxyV2.ethereum.test.ts @@ -1,12 +1,6 @@ import '@frequency-chain/api-augment'; import assert from 'assert'; -import { - createAndFundKeypair, - EcdsaSignature, - getBlockNumber, - getNonce, - Sr25519Signature, -} from '../scaffolding/helpers'; +import { createAndFundKeypair, EcdsaSignature, getNonce, Sr25519Signature } from '../scaffolding/helpers'; import { KeyringPair } from '@polkadot/keyring/types'; import { ExtrinsicHelper } from '../scaffolding/extrinsicHelpers'; import { getFundingSource } from '../scaffolding/funding'; @@ -28,11 +22,13 @@ describe('Passkey Pallet Proxy V2 Ethereum Tests', function () { }); it('should transfer via passkeys with root sr25519 key into an ethereum style account', async function () { + const startingBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); const accountPKey = getUnifiedPublicKey(fundedSr25519Keys); const nonce = await getNonce(fundedSr25519Keys); + const transferAmount = 55_000_000n; const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive( getUnifiedAddress(receiverKeys), - 55_000_000n + transferAmount ); const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedSr25519Keys.sign(u8aWrapBytes(passKeyPublicKey)); @@ -46,21 +42,56 @@ describe('Passkey Pallet Proxy V2 Ethereum Tests', function () { false ); const passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundedSr25519Keys, passkeyPayload); - await assert.doesNotReject(passkeyProxy.fundAndSendUnsigned(fundingSource)); - await ExtrinsicHelper.waitForFinalization((await getBlockNumber()) + 2); - // adding some delay before fetching the nonce to ensure it is updated - await new Promise((resolve) => setTimeout(resolve, 1000)); + try { + const { + target, + eventMap: { 'balances.Transfer': transferEvent }, + } = await passkeyProxy.fundAndSendUnsigned(fundingSource); + assert.notEqual(target, undefined, 'Target event should not be undefined'); + assert.equal( + ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent), + true, + 'Transfer event should be of correct type' + ); + if (transferEvent && ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent)) { + const { from, to, amount } = transferEvent.data; + assert.equal( + from.toString(), + getUnifiedAddress(fundedSr25519Keys), + 'From address should be the funded sr25519 key' + ); + assert.equal(to.toString(), getUnifiedAddress(receiverKeys), 'To address should be the receiver key'); + assert.equal(amount.toBigInt(), transferAmount, `Transfer amount should be ${transferAmount}`); + } + } catch (e: any) { + assert.fail(e); + } + + /* + * Normally these checks would be unnecessary, but we are testing the passkey pallet + * which has additional logic surrounding mapping account keys, so we want to make sure + * that the nonce and balance are updated correctly. + */ const nonceAfter = (await ExtrinsicHelper.getAccountInfo(fundedSr25519Keys)).nonce.toNumber(); - assert.equal(nonce + 1, nonceAfter); + assert.equal(nonce + 1, nonceAfter, 'Nonce should be incremented by 1'); + + const balanceAfter = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); + assert.equal( + balanceAfter, + startingBalance + transferAmount, + 'Receiver balance should be incremented by transfer amount' + ); }); it('should transfer via passkeys with root ethereum style key into another one', async function () { + const startingBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); const accountPKey = getUnifiedPublicKey(fundedEthereumKeys); console.log(`accountPKey ${u8aToHex(accountPKey)}`); const nonce = await getNonce(fundedEthereumKeys); + const transferAmount = 66_000_000n; const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive( getUnifiedAddress(receiverKeys), - 66_000_000n + transferAmount ); const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); // ethereum keys should not have wrapping @@ -76,12 +107,48 @@ describe('Passkey Pallet Proxy V2 Ethereum Tests', function () { false ); const passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundingSource, passkeyPayload); - await assert.doesNotReject(passkeyProxy.sendUnsigned()); - await ExtrinsicHelper.waitForFinalization((await getBlockNumber()) + 2); - // adding some delay before fetching the nonce to ensure it is updated - await new Promise((resolve) => setTimeout(resolve, 1000)); + try { + const { + target, + eventMap: { 'balances.Transfer': transferEvent }, + } = await passkeyProxy.sendUnsigned(); + assert.notEqual(target, undefined, 'Target event should not be undefined'); + + assert.equal( + ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent), + true, + 'Transfer event should be of correct type' + ); + if (transferEvent && ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent)) { + const { from, to, amount } = transferEvent.data; + assert.equal( + from.toString(), + getUnifiedAddress(fundedEthereumKeys), + 'From address should be the funded ethereum key' + ); + assert.equal(to.toString(), getUnifiedAddress(receiverKeys), 'To address should be the receiver key'); + assert.equal(amount.toBigInt(), transferAmount, `Transfer amount should be ${transferAmount}`); + } else { + assert.fail('Transfer event not found'); + } + } catch (e: any) { + assert.fail(e); + } + + /* + * Normally these checks would be unnecessary, but we are testing the passkey pallet + * which has additional logic surrounding mapping account keys, so we want to make sure + * that the nonce and balance are updated correctly. + */ const nonceAfter = (await ExtrinsicHelper.getAccountInfo(fundedEthereumKeys)).nonce.toNumber(); - assert.equal(nonce + 1, nonceAfter); + assert.equal(nonce + 1, nonceAfter, 'Nonce should be incremented by 1'); + + const balanceAfter = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); + assert.equal( + balanceAfter, + startingBalance + transferAmount, + 'Receiver balance should be incremented by transfer amount' + ); }); }); }); diff --git a/e2e/passkey/passkeyProxyV2.test.ts b/e2e/passkey/passkeyProxyV2.test.ts index d39a4fecfe..91a18d51c6 100644 --- a/e2e/passkey/passkeyProxyV2.test.ts +++ b/e2e/passkey/passkeyProxyV2.test.ts @@ -6,7 +6,7 @@ import { ExtrinsicHelper } from '../scaffolding/extrinsicHelpers'; import { getFundingSource } from '../scaffolding/funding'; import { u8aToHex, u8aWrapBytes } from '@polkadot/util'; import { createPassKeyAndSignAccount, createPassKeyCallV2, createPasskeyPayloadV2 } from '../scaffolding/P256'; -import { getUnifiedPublicKey } from '../scaffolding/ethereum'; +import { getUnifiedAddress, getUnifiedPublicKey } from '../scaffolding/ethereum'; const fundingSource = getFundingSource(import.meta.url); describe('Passkey Pallet Proxy V2 Tests', function () { @@ -24,7 +24,7 @@ describe('Passkey Pallet Proxy V2 Tests', function () { const nonce = await getNonce(fundedKeys); const remarksCalls = ExtrinsicHelper.api.tx.system.remark('passkey-test'); - const { passKeyPrivateKey, passKeyPublicKey, passkeySignature } = createPassKeyAndSignAccount(accountPKey); + const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedKeys.sign(u8aWrapBytes(passKeyPublicKey)); const multiSignature: Sr25519Signature = { Sr25519: u8aToHex(accountSignature) }; const passkeyCall = await createPassKeyCallV2(accountPKey, nonce, remarksCalls); @@ -44,7 +44,7 @@ describe('Passkey Pallet Proxy V2 Tests', function () { const accountPKey = getUnifiedPublicKey(fundedKeys); const nonce = await getNonce(fundedKeys); const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(getUnifiedPublicKey(receiverKeys), 0n); - const { passKeyPrivateKey, passKeyPublicKey, passkeySignature } = createPassKeyAndSignAccount(accountPKey); + const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedKeys.sign('badPasskeyPublicKey'); const multiSignature: Sr25519Signature = { Sr25519: u8aToHex(accountSignature) }; const passkeyCall = await createPassKeyCallV2(accountPKey, nonce, transferCalls); @@ -64,7 +64,7 @@ describe('Passkey Pallet Proxy V2 Tests', function () { const accountPKey = getUnifiedPublicKey(fundedKeys); const nonce = await getNonce(fundedKeys); const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(getUnifiedPublicKey(receiverKeys), 0n); - const { passKeyPrivateKey, passKeyPublicKey, passkeySignature } = createPassKeyAndSignAccount(accountPKey); + const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedKeys.sign(u8aWrapBytes(passKeyPublicKey)); const multiSignature: Sr25519Signature = { Sr25519: u8aToHex(accountSignature) }; const passkeyCall = await createPassKeyCallV2(accountPKey, nonce, transferCalls); @@ -81,11 +81,13 @@ describe('Passkey Pallet Proxy V2 Tests', function () { }); it('should transfer small balance from fundedKeys to receiverKeys', async function () { + const startingBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); const accountPKey = getUnifiedPublicKey(fundedKeys); const nonce = await getNonce(fundedKeys); + const transferAmount = 100_000_000n; const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive( getUnifiedPublicKey(receiverKeys), - 100_000_000n + transferAmount ); const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); const accountSignature = fundedKeys.sign(u8aWrapBytes(passKeyPublicKey)); @@ -99,14 +101,41 @@ describe('Passkey Pallet Proxy V2 Tests', function () { false ); const passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundedKeys, passkeyPayload); - await assert.doesNotReject(passkeyProxy.fundAndSendUnsigned(fundingSource)); - await ExtrinsicHelper.waitForFinalization((await getBlockNumber()) + 2); - const receiverBalance = await ExtrinsicHelper.getAccountInfo(receiverKeys); - // adding some delay before fetching the nonce to ensure it is updated - await new Promise((resolve) => setTimeout(resolve, 2000)); + try { + const { + target, + eventMap: { 'balances.Transfer': transferEvent }, + } = await passkeyProxy.fundAndSendUnsigned(fundingSource); + assert.notEqual(target, undefined, 'Target event should not be undefined'); + assert.equal( + ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent), + true, + 'Transfer event should be of correct type' + ); + if (transferEvent && ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent)) { + const { from, to, amount } = transferEvent.data; + assert.equal(from.toString(), getUnifiedAddress(fundedKeys), 'From address should be the funded key'); + assert.equal(to.toString(), getUnifiedAddress(receiverKeys), 'To address should be the receiver key'); + assert.equal(amount.toBigInt(), transferAmount, `Transfer amount should be ${transferAmount}`); + } + } catch (e: any) { + assert.fail(e); + } + + /* + * Normally these checks would be unnecessary, but we are testing the passkey pallet + * which has additional logic surrounding mapping account keys, so we want to make sure + * that the nonce and balance are updated correctly. + */ const nonceAfter = (await ExtrinsicHelper.getAccountInfo(fundedKeys)).nonce.toNumber(); - assert.equal(nonce + 1, nonceAfter); - assert(receiverBalance.data.free.toBigInt() > 0n); + assert.equal(nonce + 1, nonceAfter, 'Nonce should be incremented by 1'); + + const balanceAfter = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); + assert.equal( + balanceAfter, + startingBalance + transferAmount, + 'Receiver balance should be incremented by transfer amount' + ); }); }); }); diff --git a/e2e/scaffolding/extrinsicHelpers.ts b/e2e/scaffolding/extrinsicHelpers.ts index 26454538d7..a109b7e1b1 100644 --- a/e2e/scaffolding/extrinsicHelpers.ts +++ b/e2e/scaffolding/extrinsicHelpers.ts @@ -3,7 +3,7 @@ import { ApiPromise, ApiRx } from '@polkadot/api'; import { ApiTypes, AugmentedEvent, SubmittableExtrinsic, SignerOptions } from '@polkadot/api/types'; import { KeyringPair } from '@polkadot/keyring/types'; import { Compact, u128, u16, u32, u64, Vec, Option, Bool } from '@polkadot/types'; -import { FrameSystemAccountInfo, PalletPasskeyPasskeyPayload, SpRuntimeDispatchError } from '@polkadot/types/lookup'; +import { FrameSystemAccountInfo, SpRuntimeDispatchError } from '@polkadot/types/lookup'; import { AnyJson, AnyNumber, AnyTuple, Codec, IEvent, ISubmittableResult } from '@polkadot/types/types'; import { firstValueFrom, filter, map, pipe, tap } from 'rxjs'; import { getBlockNumber, getExistentialDeposit, getFinalizedBlockNumber, log, MultiSignatureType } from './helpers'; @@ -290,7 +290,19 @@ export class Extrinsic { + // If we learn a transaction has an error status (this does NOT include RPC errors) + // Then throw an error + if (result.isError) { + throw new CallError(result, `Failed Transaction for ${this.event?.meta.name || 'unknown'}`); + } + }), + filter(({ status }) => status.isInBlock || status.isFinalized), + this.parseResult(this.event) + ) + ); } catch (e) { console.error(e); if ((e as any).name === 'RpcError') { From f4874d9c9241357002be766783c35c95897febcf Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Mon, 30 Dec 2024 17:00:47 -0500 Subject: [PATCH 2/3] fix: remove duplicate code now that sendUnsigned is fixed --- e2e/scaffolding/extrinsicHelpers.ts | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/e2e/scaffolding/extrinsicHelpers.ts b/e2e/scaffolding/extrinsicHelpers.ts index a109b7e1b1..e22292a79c 100644 --- a/e2e/scaffolding/extrinsicHelpers.ts +++ b/e2e/scaffolding/extrinsicHelpers.ts @@ -264,27 +264,7 @@ export class Extrinsic { - // If we learn a transaction has an error status (this does NOT include RPC errors) - // Then throw an error - if (result.isError) { - throw new CallError(result, `Failed Transaction for ${this.event?.meta.name || 'unknown'}`); - } - }), - filter(({ status }) => status.isInBlock || status.isFinalized), - this.parseResult(this.event) - ) - ); - } catch (e) { - if ((e as any).name === 'RpcError') { - console.error("WARNING: Unexpected RPC Error! If it is expected, use 'current' for the nonce."); - } - throw e; - } + return this.sendUnsigned(); } public async sendUnsigned() { From e156c556390e9e4a9c8eed86afd0392d6babd510 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Thu, 2 Jan 2025 15:01:48 -0500 Subject: [PATCH 3/3] feat: make passkey tests more granular and verbose, check for specific errors on failures --- e2e/passkey/passkeyProxyV2.ethereum.test.ts | 139 +++++++++++++------- e2e/passkey/passkeyProxyV2.test.ts | 132 +++++++++++++------ 2 files changed, 182 insertions(+), 89 deletions(-) diff --git a/e2e/passkey/passkeyProxyV2.ethereum.test.ts b/e2e/passkey/passkeyProxyV2.ethereum.test.ts index 1f06208d68..6e9b0c2464 100644 --- a/e2e/passkey/passkeyProxyV2.ethereum.test.ts +++ b/e2e/passkey/passkeyProxyV2.ethereum.test.ts @@ -2,11 +2,13 @@ import '@frequency-chain/api-augment'; import assert from 'assert'; import { createAndFundKeypair, EcdsaSignature, getNonce, Sr25519Signature } from '../scaffolding/helpers'; import { KeyringPair } from '@polkadot/keyring/types'; -import { ExtrinsicHelper } from '../scaffolding/extrinsicHelpers'; +import { Extrinsic, ExtrinsicHelper } from '../scaffolding/extrinsicHelpers'; import { getFundingSource } from '../scaffolding/funding'; import { getUnifiedPublicKey, getUnifiedAddress } from '../scaffolding/ethereum'; import { createPassKeyAndSignAccount, createPassKeyCallV2, createPasskeyPayloadV2 } from '../scaffolding/P256'; import { u8aToHex, u8aWrapBytes } from '@polkadot/util'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { ISubmittableResult } from '@polkadot/types/types'; const fundingSource = getFundingSource(import.meta.url); describe('Passkey Pallet Proxy V2 Ethereum Tests', function () { @@ -83,72 +85,113 @@ describe('Passkey Pallet Proxy V2 Ethereum Tests', function () { ); }); - it('should transfer via passkeys with root ethereum style key into another one', async function () { - const startingBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); - const accountPKey = getUnifiedPublicKey(fundedEthereumKeys); - console.log(`accountPKey ${u8aToHex(accountPKey)}`); - const nonce = await getNonce(fundedEthereumKeys); + describe('successful transfer via passkeys with root ethereum-style key into a polkadot-style key', function () { + let startingReceiverBalance: bigint; + let startingSenderBalance: bigint; + let accountPKey: Uint8Array; + let nonce: number; + let target: any; + let transferEvent: any; + let feeEvent: any; + let passkeyProxy: Extrinsic< + { + accountId: AccountId32; + }, + ISubmittableResult, + [accountId: AccountId32] + >; + const transferAmount = 66_000_000n; - const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive( - getUnifiedAddress(receiverKeys), - transferAmount - ); - const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); - // ethereum keys should not have wrapping - const accountSignature = fundedEthereumKeys.sign(passKeyPublicKey); - console.log(`accountSignature ${u8aToHex(accountSignature)}`); - const multiSignature: EcdsaSignature = { Ecdsa: u8aToHex(accountSignature) }; - const passkeyCall = await createPassKeyCallV2(accountPKey, nonce, transferCalls); - const passkeyPayload = await createPasskeyPayloadV2( - multiSignature, - passKeyPrivateKey, - passKeyPublicKey, - passkeyCall, - false - ); - const passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundingSource, passkeyPayload); - try { - const { - target, - eventMap: { 'balances.Transfer': transferEvent }, - } = await passkeyProxy.sendUnsigned(); - assert.notEqual(target, undefined, 'Target event should not be undefined'); + before(async function () { + startingReceiverBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); + startingSenderBalance = (await ExtrinsicHelper.getAccountInfo(fundedEthereumKeys)).data.free.toBigInt(); + accountPKey = getUnifiedPublicKey(fundedEthereumKeys); + console.log(`accountPKey ${u8aToHex(accountPKey)}`); + nonce = await getNonce(fundedEthereumKeys); + const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive( + getUnifiedAddress(receiverKeys), + transferAmount + ); + const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); + // ethereum keys should not have wrapping + const accountSignature = fundedEthereumKeys.sign(passKeyPublicKey); + console.log(`accountSignature ${u8aToHex(accountSignature)}`); + const multiSignature: EcdsaSignature = { Ecdsa: u8aToHex(accountSignature) }; + const passkeyCall = await createPassKeyCallV2(accountPKey, nonce, transferCalls); + const passkeyPayload = await createPasskeyPayloadV2( + multiSignature, + passKeyPrivateKey, + passKeyPublicKey, + passkeyCall, + false + ); + passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundingSource, passkeyPayload); + }); + + it('should successfully execute transfer extrinsic', async function () { + await assert.doesNotReject(async () => { + ({ + target, + eventMap: { 'balances.Transfer': transferEvent, 'balances.Withdraw': feeEvent }, + } = await passkeyProxy.sendUnsigned()); + }); + }); + + it('should have received a transaction execution success event', async function () { + assert.notEqual(target, undefined, 'Target event should not be undefined'); assert.equal( - ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent), + ExtrinsicHelper.api.events.passkey.TransactionExecutionSuccess.is(target), true, - 'Transfer event should be of correct type' + 'Target event should be of correct type' ); + }); + + it('should have debited & credited correct accounts for the correct amount', async function () { if (transferEvent && ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent)) { const { from, to, amount } = transferEvent.data; - assert.equal( - from.toString(), - getUnifiedAddress(fundedEthereumKeys), - 'From address should be the funded ethereum key' - ); + assert.equal(from.toString(), getUnifiedAddress(fundedEthereumKeys), 'From address should be the funded key'); assert.equal(to.toString(), getUnifiedAddress(receiverKeys), 'To address should be the receiver key'); assert.equal(amount.toBigInt(), transferAmount, `Transfer amount should be ${transferAmount}`); } else { assert.fail('Transfer event not found'); } - } catch (e: any) { - assert.fail(e); - } + }); + + it('should have deducted fee from the sender', async function () { + if (feeEvent && ExtrinsicHelper.api.events.balances.Withdraw.is(feeEvent)) { + const { who, amount } = feeEvent.data; + assert.equal(who.toString(), getUnifiedAddress(fundedEthereumKeys), 'Fee should be deducted from the sender'); + assert.equal(amount.toBigInt() > 0, true, 'Fee should be greater than 0'); + } else { + assert.fail('Fee event not found'); + } + }); /* * Normally these checks would be unnecessary, but we are testing the passkey pallet * which has additional logic surrounding mapping account keys, so we want to make sure * that the nonce and balance are updated correctly. */ - const nonceAfter = (await ExtrinsicHelper.getAccountInfo(fundedEthereumKeys)).nonce.toNumber(); - assert.equal(nonce + 1, nonceAfter, 'Nonce should be incremented by 1'); + it('should have incremented nonce by 1 for sender', async function () { + const nonceAfter = (await ExtrinsicHelper.getAccountInfo(fundedEthereumKeys)).nonce.toNumber(); + assert.equal(nonce + 1, nonceAfter, 'Nonce should be incremented by 1'); + }); - const balanceAfter = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); - assert.equal( - balanceAfter, - startingBalance + transferAmount, - 'Receiver balance should be incremented by transfer amount' - ); + it('should have changed account balances correctly', async function () { + const endingReceiverBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); + const endingSenderBalance = (await ExtrinsicHelper.getAccountInfo(fundedEthereumKeys)).data.free.toBigInt(); + assert.equal( + endingReceiverBalance, + startingReceiverBalance + transferAmount, + 'Receiver balance should be incremented by transfer amount' + ); + assert.equal( + startingSenderBalance - transferAmount >= endingSenderBalance, + true, + 'Sender balance should be decremented by at least transfer amount' + ); + }); }); }); }); diff --git a/e2e/passkey/passkeyProxyV2.test.ts b/e2e/passkey/passkeyProxyV2.test.ts index 91a18d51c6..7ab5eb5d79 100644 --- a/e2e/passkey/passkeyProxyV2.test.ts +++ b/e2e/passkey/passkeyProxyV2.test.ts @@ -2,11 +2,13 @@ import '@frequency-chain/api-augment'; import assert from 'assert'; import { createAndFundKeypair, getBlockNumber, getNonce, Sr25519Signature } from '../scaffolding/helpers'; import { KeyringPair } from '@polkadot/keyring/types'; -import { ExtrinsicHelper } from '../scaffolding/extrinsicHelpers'; +import { Extrinsic, ExtrinsicHelper } from '../scaffolding/extrinsicHelpers'; import { getFundingSource } from '../scaffolding/funding'; import { u8aToHex, u8aWrapBytes } from '@polkadot/util'; import { createPassKeyAndSignAccount, createPassKeyCallV2, createPasskeyPayloadV2 } from '../scaffolding/P256'; import { getUnifiedAddress, getUnifiedPublicKey } from '../scaffolding/ethereum'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { ISubmittableResult } from '@polkadot/types/types'; const fundingSource = getFundingSource(import.meta.url); describe('Passkey Pallet Proxy V2 Tests', function () { @@ -37,7 +39,7 @@ describe('Passkey Pallet Proxy V2 Tests', function () { ); const passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundedKeys, passkeyPayload); - await assert.rejects(passkeyProxy.fundAndSendUnsigned(fundingSource)); + await assert.rejects(passkeyProxy.fundAndSendUnsigned(fundingSource), /Transaction call is not expected/); }); it('should fail to transfer balance due to bad account ownership proof', async function () { @@ -57,7 +59,7 @@ describe('Passkey Pallet Proxy V2 Tests', function () { ); const passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundedKeys, passkeyPayload); - await assert.rejects(passkeyProxy.fundAndSendUnsigned(fundingSource)); + await assert.rejects(passkeyProxy.fundAndSendUnsigned(fundingSource), /Invalid signing address/); }); it('should fail to transfer balance due to bad passkey signature', async function () { @@ -77,65 +79,113 @@ describe('Passkey Pallet Proxy V2 Tests', function () { ); const passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundedKeys, passkeyPayload); - await assert.rejects(passkeyProxy.fundAndSendUnsigned(fundingSource)); + await assert.rejects(passkeyProxy.fundAndSendUnsigned(fundingSource), /Custom error: 4/); }); - it('should transfer small balance from fundedKeys to receiverKeys', async function () { - const startingBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); - const accountPKey = getUnifiedPublicKey(fundedKeys); - const nonce = await getNonce(fundedKeys); + describe('successful transfer small balance from fundedKeys to receiverKeys', function () { + let startingReceiverBalance: bigint; + let startingSenderBalance: bigint; + let accountPKey: Uint8Array; + let nonce: number; + let target: any; + let transferEvent: any; + let feeEvent: any; + let passkeyProxy: Extrinsic< + { + accountId: AccountId32; + }, + ISubmittableResult, + [accountId: AccountId32] + >; + const transferAmount = 100_000_000n; - const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive( - getUnifiedPublicKey(receiverKeys), - transferAmount - ); - const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); - const accountSignature = fundedKeys.sign(u8aWrapBytes(passKeyPublicKey)); - const multiSignature: Sr25519Signature = { Sr25519: u8aToHex(accountSignature) }; - const passkeyCall = await createPassKeyCallV2(accountPKey, nonce, transferCalls); - const passkeyPayload = await createPasskeyPayloadV2( - multiSignature, - passKeyPrivateKey, - passKeyPublicKey, - passkeyCall, - false - ); - const passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundedKeys, passkeyPayload); - try { - const { - target, - eventMap: { 'balances.Transfer': transferEvent }, - } = await passkeyProxy.fundAndSendUnsigned(fundingSource); + + before(async function () { + startingReceiverBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); + startingSenderBalance = (await ExtrinsicHelper.getAccountInfo(fundedKeys)).data.free.toBigInt(); + accountPKey = getUnifiedPublicKey(fundedKeys); + nonce = await getNonce(fundedKeys); + const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive( + getUnifiedPublicKey(receiverKeys), + transferAmount + ); + const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey); + const accountSignature = fundedKeys.sign(u8aWrapBytes(passKeyPublicKey)); + const multiSignature: Sr25519Signature = { Sr25519: u8aToHex(accountSignature) }; + const passkeyCall = await createPassKeyCallV2(accountPKey, nonce, transferCalls); + const passkeyPayload = await createPasskeyPayloadV2( + multiSignature, + passKeyPrivateKey, + passKeyPublicKey, + passkeyCall, + false + ); + passkeyProxy = ExtrinsicHelper.executePassKeyProxyV2(fundedKeys, passkeyPayload); + }); + + it('should successfully execute transfer extrinsic', async function () { + await assert.doesNotReject(async () => { + ({ + target, + eventMap: { 'balances.Transfer': transferEvent, 'balances.Withdraw': feeEvent }, + } = await passkeyProxy.fundAndSendUnsigned(fundingSource)); + }); + }); + + it('should have received a transaction execution success event', async function () { assert.notEqual(target, undefined, 'Target event should not be undefined'); assert.equal( ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent), true, 'Transfer event should be of correct type' ); + }); + + it('should have debited and credited correct accounts for the correct amount', async function () { if (transferEvent && ExtrinsicHelper.api.events.balances.Transfer.is(transferEvent)) { const { from, to, amount } = transferEvent.data; assert.equal(from.toString(), getUnifiedAddress(fundedKeys), 'From address should be the funded key'); assert.equal(to.toString(), getUnifiedAddress(receiverKeys), 'To address should be the receiver key'); assert.equal(amount.toBigInt(), transferAmount, `Transfer amount should be ${transferAmount}`); + } else { + assert.fail('Transfer event not found'); + } + }); + + it('should have deducted the correct fee from the sender', async function () { + if (feeEvent && ExtrinsicHelper.api.events.balances.Withdraw.is(feeEvent)) { + const { who, amount } = feeEvent.data; + assert.equal(who.toString(), getUnifiedAddress(fundedKeys), 'Fee should be deducted from the sender'); + assert.equal(amount.toBigInt() > 0n, true, 'Fee should be greater than 0'); + } else { + assert.fail('Fee event not found'); } - } catch (e: any) { - assert.fail(e); - } + }); /* * Normally these checks would be unnecessary, but we are testing the passkey pallet * which has additional logic surrounding mapping account keys, so we want to make sure * that the nonce and balance are updated correctly. */ - const nonceAfter = (await ExtrinsicHelper.getAccountInfo(fundedKeys)).nonce.toNumber(); - assert.equal(nonce + 1, nonceAfter, 'Nonce should be incremented by 1'); - - const balanceAfter = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); - assert.equal( - balanceAfter, - startingBalance + transferAmount, - 'Receiver balance should be incremented by transfer amount' - ); + it('should have incremented nonce by 1 for sender', async function () { + const nonceAfter = (await ExtrinsicHelper.getAccountInfo(fundedKeys)).nonce.toNumber(); + assert.equal(nonce + 1, nonceAfter, 'Nonce should be incremented by 1'); + }); + + it('should have changed account balances correctly', async function () { + const endingReceiverBalance = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt(); + const endingSenderBalance = (await ExtrinsicHelper.getAccountInfo(fundedKeys)).data.free.toBigInt(); + assert.equal( + endingReceiverBalance, + startingReceiverBalance + transferAmount, + 'Receiver balance should be incremented by transfer amount' + ); + assert.equal( + startingSenderBalance - transferAmount >= endingSenderBalance, + true, + 'Sender balance should be decremented by at least transfer amount' + ); + }); }); }); });