Skip to content

Commit

Permalink
test: add Send eth with a Trezor account spec (#26530)
Browse files Browse the repository at this point in the history
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**
This PR adds a new spec for sending ETH using a Trezor account.
This is based on @mikesposito 's [work for unlocking Trezor
accounts](https://github.com/MetaMask/metamask-extension/pull/25824/files#diff-68eb44d9a0523c834e912eda6dcec0d84faed3ef31c86bccadbf86bd64a54f44)
and it's a joined work with @mikesposito which helped in the
investigation of several blockers.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/26530?quickstart=1)

## **Related issues**

Fixes: MetaMask/MetaMask-planning#3015

## **Manual testing steps**

1. Check ci
2. Alternatively, run the test locally `yarn test:e2e:single
test/e2e/tests/hardware-wallets/trezor-send.spec.ts --browser=chrome`

## **Screenshots/Recordings**



https://github.com/user-attachments/assets/06ce6ae8-6296-4683-953d-50df0c84db9d





## **Pre-merge author checklist**

- [X] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [X] I've completed the PR template to the best of my ability
- [X] I’ve included tests if applicable
- [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [X] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

---------

Co-authored-by: Michele Esposito <[email protected]>
Co-authored-by: Michele Esposito <[email protected]>
  • Loading branch information
3 people authored Sep 9, 2024
1 parent 838ad26 commit 416d024
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 7 deletions.
140 changes: 140 additions & 0 deletions test/e2e/fixture-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ class FixtureBuilder {
onboarding === true ? onboardingFixture() : defaultFixture(inputChainId);
}

withAccountTracker(data) {
merge(this.fixture.data.AccountTracker, data);
return this;
}

withAddressBookController(data) {
merge(
this.fixture.data.AddressBookController
Expand Down Expand Up @@ -1791,6 +1796,141 @@ class FixtureBuilder {
return this.withNameController({ names: {} });
}

withTrezorAccount() {
return this.withAccountTracker({
accounts: {
'0x5cfe73b6021e818b776b421b1c4db2474086a7e1': {
address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
balance: '0x15af1d78b58c40000',
},
'0xf68464152d7289d7ea9a2bec2e0035c45188223c': {
address: '0xf68464152d7289d7ea9a2bec2e0035c45188223c',
balance: '0x100000000000000000000',
},
},
currentBlockGasLimit: '0x1c9c380',
accountsByChainId: {
'0x539': {
'0x5cfe73b6021e818b776b421b1c4db2474086a7e1': {
address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
balance: '0x15af1d78b58c40000',
},
'0xf68464152d7289d7ea9a2bec2e0035c45188223c': {
address: '0xf68464152d7289d7ea9a2bec2e0035c45188223c',
balance: '0x100000000000000000000',
},
},
},
currentBlockGasLimitByChainId: {
'0x539': '0x1c9c380',
},
})
.withAccountsController({
internalAccounts: {
accounts: {
'd5e45e4a-3b04-4a09-a5e1-39762e5c6be4': {
id: 'd5e45e4a-3b04-4a09-a5e1-39762e5c6be4',
address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
options: {},
methods: [
'personal_sign',
'eth_sign',
'eth_signTransaction',
'eth_signTypedData_v1',
'eth_signTypedData_v3',
'eth_signTypedData_v4',
],
type: 'eip155:eoa',
metadata: {
name: 'Account 1',
importTime: 1724486724986,
lastSelected: 1665507600000,
keyring: {
type: 'HD Key Tree',
},
},
},
'221ecb67-0d29-4c04-83b2-dff07c263634': {
id: '221ecb67-0d29-4c04-83b2-dff07c263634',
address: '0xf68464152d7289d7ea9a2bec2e0035c45188223c',
options: {},
methods: [
'personal_sign',
'eth_sign',
'eth_signTransaction',
'eth_signTypedData_v1',
'eth_signTypedData_v3',
'eth_signTypedData_v4',
],
type: 'eip155:eoa',
metadata: {
name: 'Trezor 1',
importTime: 1724486729079,
keyring: {
type: 'Trezor Hardware',
},
lastSelected: 1724486729083,
},
},
},
selectedAccount: '221ecb67-0d29-4c04-83b2-dff07c263634',
},
})
.withKeyringController({
vault:
'{"data":"NPUZE4s9SQOrsw1GtJSnQ9ptC3J1nf3O+hWT3N8Oh5MDcyO0XojQfSBZL88FgjuAGMT+oFEnX8gzsd1x0/Z7iinNSOD+U22LJ6w37Pkfw4mqAYvKJDbnb2HAdjNbjGD99PKn1qe5eR0vohL5taFW2lTKdlE3dficITFM9wm9mQTegQVvYClTSktweumFSTMxqO1fUPj7oacLmw69ZAk2/am4fhI4c6ZeJoAkvPTJvYZDOne3WkUlcuUoeJjCX7b/59NQNHeCry8OyWVMCZDMYFsJT9Pk2vlFgnVL69n9dRGHrZNuNGFOhFawta5TqDUn1Ya7Iq0FjBW1WQv+HKktMM+RA8KZZyAAJkXYHRMpmUhQkw4wQFELgHjKFm/NIYcFVT5t6/XIj9kLqh4+55krUGoEHygzX41uSNie/wNmLjTgNAZv/eK9R81vyv1FR8N1fgkr13KxQT/0o/bQZhnaVClFa/3t13epiRrU/1plVh2TaI7HLFLj69d4c7w96J7Z33osjCywpNCJLam3Xx5OLAaPVe+L7a9u/zOMmryxX37xCrQhn9YSzZ0+E9Hik9CZU9ZXqmNgRhYAoqpcRWgMVmEC2HRLBIXXF0VTyYvfUvEfn87iAsqw0KeoQagDpUPsEr8UU9zs6cGRqZZTfR6/Wa3UwuIwV5XnCRg3Eifiz2BHKG4kutxKIJJak9habIfXBjxMrrwrHns7tWmWmE3JRYoekJQxFdWP3mcnDHVNz2VscgWeW5bZEoBim91iPRbsXimX9605xE0WOaHpwu27G9LwTNwL+0f8BgwoCcfMbaKwoDGVqKFOSbKurYBByPmWsm1b10vVrnsxA3VZMd2HWhicD7DE5h/4R+7Z90VthpVwt4NQ7+QmXeSXqCpPcoq7UTrchdYgV95xbKna1r0lSnZSfUMALji1I2Nh96ki24SbbUEeFZGm4dxNSnub07hTKF6xeqS1FvV79hBpZi/6v+pS+SDNSlwEcfRWW3S02Ec6JAhK2rVCQqSwasFcVcznYB5OaKL6QCmriIpqH0ATsthAwsf9naHSU+36wwi3xogxbpzecjaZ8gxKs2wmJk+Rz6VoGB+z9DTzvha5sm4DmfuQ2CtbQNYZq20VG3hO9g7wzWwa5xZmbH7njBDqlpaNgmxMrAX1S+T8D7X6ElD+aH0MyP9UD5E5tT5xxgUAV0wi+LY0+uCi2Y2lragFM7ihmPr1MP5wEy/1eIf45cY3imfl9w0F/FrCo+Hy2Au9AueCCab2eabA8QAum3lhXtdOyc123sSghIPjC6RUlZE53skLx1cPaV5JJAkneQJ44QMWecLQjh3YyCzRQ8XCnFAL+Kmf7zW5t+l25PLCkcfuLE7zxvLsTz3w2TCIXzEJyw1vXjBzPTUdKCNSva0WGsbq5B93zYot6bmvK1RKHeje8Ed/4N/l8uwxulUAjYQ+94qDKkxTVxvAZ8ydoxwKuB8QCTXgbymDsF/Y5l+RDXmzMT8BdN/QtdjsCXJ2PjvBG+srQOPntOCZMS7FVMk9yc6MWE/DBDm7HtY5CiY3af4A5sOZmLSP3Ek91ijmYdr/nO32DnkV4NJ2/Hj8SWAK5OD8zq8q5uRlR8BDcj7oLnzJX4S+yJNJ/nZSleUyTsv5v6YZ8hno","iv":"6SgfUVcvgUDGbCuqmdZgbA==","keyMetadata":{"algorithm":"PBKDF2","params":{"iterations":600000}},"salt":"nk4xdpmMR+1s5BYe4Vnk++XAQwrISI2bCtbMg7V1wUA="}',
})
.withNameController({
names: {
ethereumAddress: {
'0x5cfe73b6021e818b776b421b1c4db2474086a7e1': {
'*': {
name: 'Account 1',
sourceId: null,
proposedNames: {},
origin: 'account-identity',
},
},
'0xf68464152d7289d7ea9a2bec2e0035c45188223c': {
'*': {
proposedNames: {},
name: 'Trezor 1',
sourceId: null,
origin: 'account-identity',
},
},
},
},
})
.withPreferencesController({
identities: {
'0x5cfe73b6021e818b776b421b1c4db2474086a7e1': {
address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
lastSelected: 1665507600000,
name: 'Account 1',
},
'0xf68464152d7289d7ea9a2bec2e0035c45188223c': {
address: '0xf68464152d7289d7ea9a2bec2e0035c45188223c',
lastSelected: 1665507800000,
name: 'Trezor 1',
},
},
lostIdentities: {
'0x5cfe73b6021e818b776b421b1c4db2474086a7e1': {
address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1',
name: 'Account 1',
lastSelected: 1665507600000,
},
'0xf68464152d7289d7ea9a2bec2e0035c45188223c': {
address: '0xf68464152d7289d7ea9a2bec2e0035c45188223c',
name: 'Trezor 1',
lastSelected: 1665507800000,
},
},
selectedAddress: '0xf68464152d7289d7ea9a2bec2e0035c45188223c',
});
}

build() {
this.fixture.meta = {
version: FIXTURE_STATE_METADATA_VERSION,
Expand Down
7 changes: 7 additions & 0 deletions test/e2e/seeder/ganache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ export class Ganache {
return Number(fiatBalance);
}

async setAccountBalance(address: string, balance: string) {
return await this.getProvider()?.request({
method: 'evm_setAccountBalance',
params: [address, balance],
});
}

async quit() {
if (!this.#server) {
throw new Error('Server not running yet');
Expand Down
47 changes: 47 additions & 0 deletions test/e2e/tests/hardware-wallets/trezor-send.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Suite } from 'mocha';
import { Driver } from '../../webdriver/driver';
import { Ganache } from '../../seeder/ganache';
import FixtureBuilder from '../../fixture-builder';
import {
defaultGanacheOptions,
logInWithBalanceValidation,
sendTransaction,
withFixtures,
} from '../../helpers';
import { KNOWN_PUBLIC_KEY_ADDRESSES } from '../../../stub/keyring-bridge';

const RECIPIENT = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3';

describe('Trezor Hardware', function (this: Suite) {
it('send ETH', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder().withTrezorAccount().build(),
ganacheOptions: defaultGanacheOptions,
title: this.test?.fullTitle(),
},
async ({
driver,
ganacheServer,
}: {
driver: Driver;
ganacheServer?: Ganache;
}) => {
// Seed the Trezor account with balance
await ganacheServer?.setAccountBalance(
KNOWN_PUBLIC_KEY_ADDRESSES[0].address,
'0x100000000000000000000',
);
await logInWithBalanceValidation(driver);

await sendTransaction(driver, RECIPIENT, '1');

// Wait for transaction to be confirmed
await driver.waitForSelector({
css: '.transaction-status-label',
text: 'Confirmed',
});
},
);
});
});
59 changes: 52 additions & 7 deletions test/stub/keyring-bridge.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
import { Transaction } from '@ethereumjs/tx';
import { bufferToHex } from 'ethereumjs-util';
import { addHexPrefix, Common } from './keyring-utils';

// BIP32 Public Key: xpub6ELgkkwgfoky9h9fFu4Auvx6oHvJ6XfwiS1NE616fe9Uf4H3JHtLGjCePVkb6RFcyDCqVvjXhNXbDNDqs6Kjoxw7pTAeP1GSEiLHmA5wYa9
// BIP32 Private Key: xprvA1MLMFQnqSCfwD5C9sXAYo1NFG5oh4x6MD5mRhbV7JcVnFwtkka5ivtAYDYJsr9GS242p3QZMbsMZC1GZ2uskNeTj9VhYxrCqRG6U5UPXp5
export const KNOWN_PUBLIC_KEY =
'02065bc80d3d12b3688e4ad5ab1e9eda6adf24aec2518bfc21b87c99d4c5077ab0';
'03752603a8131fd03fe726434e82a181c3a6bc227a44660ab774a482d29d1172c3';

export const CHAIN_CODE =
'2b73df9ce5df820c728c8f77d51a72ec578e25c6a3c5e32b65fd43d2b4fb0e63';

export const KNOWN_PUBLIC_KEY_ADDRESSES = [
{
address: '0x0e122670701207DB7c6d7ba9aE07868a4572dB3f',
address: '0xF68464152d7289D7eA9a2bEC2E0035c45188223c',
balance: null,
index: 0,
},
{
address: '0x2ae19DAd8b2569F7Bb4606D951Cc9495631e818E',
address: '0x9EE70472c9D1B1679A33f2f0549Ab5BFFCE118eF',
balance: null,
index: 1,
},
{
address: '0x0051140bAaDC3E9AC92A4a90D18Bb6760c87e7ac',
address: '0x3185aC9266D3DF3D95dC847e2B88b52F12A34C21',
balance: null,
index: 2,
},
{
address: '0x9DBCF67CC721dBd8Df28D7A0CbA0fa9b0aFc6472',
address: '0x49EED7a86c1C404e2666Ac12BF00Af63804AC78d',
balance: null,
index: 3,
},
{
address: '0x828B2c51c5C1bB0c57fCD2C108857212c95903DE',
address: '0x1d374341feBd02C2F30929d2B4a767676799E1f2',
balance: null,
index: 4,
},
];

export const KNOWN_PRIVATE_KEYS = [
'd41051826c32a548e55aa3e0dee93e96425b0f355df1e06d1595ed69385f8dc3',
'780f45733fe48f03ab993b071a11e77147ca959d417e048c7da5ac06b8283e51',
'daf3144f471e0531e5efd6e81b4907a4154fec5fdb53cf4f94c4b4195e6473fb',
'841f90906439526b3771c0aa51f93f6aae5c5ee0fdc73d0d8ff7f8a9b28754d7',
'7df6c85f059939631c05e72b6fc3c54423754a5162ae4a69b14b38219c430665',
];

export class FakeKeyringBridge {
#publicKeyPayload;

Expand All @@ -52,7 +69,8 @@ export class FakeTrezorBridge extends FakeKeyringBridge {
success: true,
payload: {
publicKey: KNOWN_PUBLIC_KEY,
chainCode: '0x1',
chainCode: CHAIN_CODE,
address: KNOWN_PUBLIC_KEY_ADDRESSES[0].address,
},
},
});
Expand All @@ -61,6 +79,33 @@ export class FakeTrezorBridge extends FakeKeyringBridge {
async dispose() {
return Promise.resolve();
}

async ethereumSignTransaction({ transaction }) {
const common = Common.custom({
chain: {
name: 'localhost',
chainId: transaction.chainId,
networkId: transaction.chainId,
},
chainId: transaction.chainId,
hardfork: 'istanbul',
});

const signedTransaction = Transaction.fromTxData(transaction, {
common,
}).sign(Buffer.from(KNOWN_PRIVATE_KEYS[0], 'hex'));

return {
id: 1,
success: true,
payload: {
v: signedTransaction.v,
r: signedTransaction.r,
s: signedTransaction.s,
serializedTx: addHexPrefix(bufferToHex(signedTransaction.serialize())),
},
};
}
}

export class FakeLedgerBridge extends FakeKeyringBridge {
Expand Down
Loading

0 comments on commit 416d024

Please sign in to comment.