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

fix: make e2e tests for passkey more rigorous in their success checks #2246

Merged
merged 3 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 152 additions & 42 deletions e2e/passkey/passkeyProxyV2.ethereum.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
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 { 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 () {
Expand All @@ -28,11 +24,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;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we have constants for these like DOLLARS and CENTS?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure, but the amount isn't important anyway; we just want to be sure to check that whatever amount is transferred, the balance changes by that amount.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But that does remind me, it would be good to also check that the correct sender's balance decreases by the appropriate amount.

const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(
getUnifiedAddress(receiverKeys),
55_000_000n
transferAmount
);
const { passKeyPrivateKey, passKeyPublicKey } = createPassKeyAndSignAccount(accountPKey);
const accountSignature = fundedSr25519Keys.sign(u8aWrapBytes(passKeyPublicKey));
Expand All @@ -46,42 +44,154 @@ 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));
Comment on lines -49 to -52
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't need to await finalization now that sendUnsigned has been fixed to not resolve until the extrinsic is included in a finalized block.

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');

it('should transfer via passkeys with root ethereum style key into another one', async function () {
const accountPKey = getUnifiedPublicKey(fundedEthereumKeys);
console.log(`accountPKey ${u8aToHex(accountPKey)}`);
const nonce = await getNonce(fundedEthereumKeys);
const transferCalls = ExtrinsicHelper.api.tx.balances.transferKeepAlive(
getUnifiedAddress(receiverKeys),
66_000_000n
const balanceAfter = (await ExtrinsicHelper.getAccountInfo(receiverKeys)).data.free.toBigInt();
assert.equal(
balanceAfter,
startingBalance + transferAmount,
'Receiver balance should be incremented by transfer amount'
);
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);
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));
const nonceAfter = (await ExtrinsicHelper.getAccountInfo(fundedEthereumKeys)).nonce.toNumber();
assert.equal(nonce + 1, nonceAfter);
});

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<ArrayBufferLike>;
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;

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.passkey.TransactionExecutionSuccess.is(target),
true,
'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 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 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.
*/
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');
});

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'
);
});
});
});
});
Loading
Loading