From 85d3dcb88214071115a1677daed7841927b7b16f Mon Sep 17 00:00:00 2001 From: cfranceschi-ledger Date: Wed, 4 Sep 2024 16:40:39 +0200 Subject: [PATCH 1/7] invert instruction --- pages/docs/blockchain/setup-build.mdx | 2 +- pages/docs/blockchain/testing.mdx | 36 +++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pages/docs/blockchain/setup-build.mdx b/pages/docs/blockchain/setup-build.mdx index d9c03017..8b8c8773 100644 --- a/pages/docs/blockchain/setup-build.mdx +++ b/pages/docs/blockchain/setup-build.mdx @@ -107,7 +107,7 @@ Here is a typical family folder structure (TS integration): #### Ledger live-common * `src/logic.ts`: Lists coin specific business logic. * `src/react.ts`: Defines and lists coin specfic react hooks for UI. - * `src/setup.ts`: Glue file for coin integration. + * `src/setup.ts`: Declares the signer in LLC. * `src/types.ts`: Lists coin specific types. #### LedgerJS diff --git a/pages/docs/blockchain/testing.mdx b/pages/docs/blockchain/testing.mdx index 14453a51..5aa92be4 100644 --- a/pages/docs/blockchain/testing.mdx +++ b/pages/docs/blockchain/testing.mdx @@ -202,6 +202,16 @@ We tried to cover as many cases as possible that are in `getTransactionStatus`. You are free to define your own extra tests in `bridge.integration.test.ts` (or any other integration test file) for more advanced tests that would not be covered by the bridge generic tests. +### Launch the test + +To launch the test do: + +```bash copy +pnpm common jest --runTestsByPath src/families//bridge.integration.test.ts +``` + +Replace `` with the name of your coin. + ## Testing the transaction broadcast with the bot Transaction broadcast is an exception, it is tested differently, by a tool that we call "the bot". See below. @@ -216,18 +226,6 @@ Transaction broadcast is an exception, it is tested differently, by a tool that We are testing the broadcast part and sync part. -### How it works - -```sh copy -pnpm run:cli cleanSpeculos; SEED="generate a seed for testing" COINAPPS="/path/to/coin/apps/folder" pnpm run:cli bot -c mycoin -``` - -- Generate a 24 words SEED, [iancoleman.io/bip39/](https://iancoleman.io/bip39/). Use this seed for testing purpose only, then use the command before to have an adresse and send some currencies into it - -- The bot will execute each scenario if it met the requirement, then it will wait until the sync find the broadcasted transaction - -- You also need to specify how the bot will react when he encounter certain screen, create `speculos-deviceActions.ts` - ### How to define a test `speculos-deviceActions.ts` @@ -386,12 +384,14 @@ For instance you can customize the embedded app version using the `appQuery` obj You can find the full list of available parameters [here](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/coin-framework/src/bot/types.ts) for AppSpec and MutationSpec. -### Launch the test +### Launch the bot -To launch the test do: - -```bash copy -pnpm common jest --runTestsByPath src/families//bridge.integration.test.ts +```sh copy +pnpm run:cli cleanSpeculos; SEED="generate a seed for testing" COINAPPS="/path/to/coin/apps/folder" pnpm run:cli bot -c mycoin ``` -Replace `` with the name of your coin. +- Generate a 24 words SEED, [iancoleman.io/bip39/](https://iancoleman.io/bip39/). Use this seed for testing purpose only, then use the command before to have an adresse and send some currencies into it + +- The bot will execute each scenario if it met the requirement, then it will wait until the sync find the broadcasted transaction + +- You also need to specify how the bot will react when he encounter certain screen, create `speculos-deviceActions.ts` From e24b65fe34f59d877e47cb8c48175c77e2d6205b Mon Sep 17 00:00:00 2001 From: cfranceschi-ledger Date: Thu, 5 Sep 2024 14:24:11 +0200 Subject: [PATCH 2/7] update --- pages/docs/blockchain/setup-build.mdx | 7 ++++ pages/docs/blockchain/testing.mdx | 58 ++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/pages/docs/blockchain/setup-build.mdx b/pages/docs/blockchain/setup-build.mdx index 8b8c8773..01b785d8 100644 --- a/pages/docs/blockchain/setup-build.mdx +++ b/pages/docs/blockchain/setup-build.mdx @@ -97,6 +97,9 @@ Here is a typical family folder structure (TS integration): #### Coin-module + +In the coin module, there is shared logic but blockchain specific, and how signer needs to be injected. + * `src/api` directory: Contains all the logic to send requests to the explorer. * `src/bridge/js.ts` file: Entry point of Ledger Live integration of the coin-module. This file lists `BridgeCurrency` and `BridgeAccount` implementation. * `src/hw-getAddress.ts` file: Logic on how to interact with device-app to retrieve addresses. @@ -105,6 +108,10 @@ Here is a typical family folder structure (TS integration): * `src/specs.ts` file: Definition of tests to be run on the bot. #### Ledger live-common + +In live-common, there is the signer implementation, the logic shared between Ledger Live Desktop and mobile, and device app specific code. + + * `src/logic.ts`: Lists coin specific business logic. * `src/react.ts`: Defines and lists coin specfic react hooks for UI. * `src/setup.ts`: Declares the signer in LLC. diff --git a/pages/docs/blockchain/testing.mdx b/pages/docs/blockchain/testing.mdx index 5aa92be4..d8f1dba0 100644 --- a/pages/docs/blockchain/testing.mdx +++ b/pages/docs/blockchain/testing.mdx @@ -226,11 +226,22 @@ Transaction broadcast is an exception, it is tested differently, by a tool that We are testing the broadcast part and sync part. +The bot executes each possible `mutation` on one account as long as it is possible and will stop if the `maxRun` is reached, or if it encounter an `invariant`. + +The bot will then move to the next account and execute the same actions, until it reaches the configured `maxAccount`. + +Then it will execute all the `updates` of the `Transaction` object. + +The bot will try to sign the transaction using instructions that you provided in `speculos-deviceActions.ts` + +Eventually it will broadcast the transaction in the blockchain and wait for the `sync` to find the operation and its optimisic version. + ### How to define a test -`speculos-deviceActions.ts` +#### speculos-deviceActions.ts + +It is required to know every screen that your device app contains, it will use the `title` of the screen then optionally check if the `expectedValue` of that screen is what it expects, then eventually execute the `button` action. -It is required to know every screen that your embedded app contains, it will use the `title` of the screen then optionally check if the `expectedValue` of that screen is what it expects, then eventually execute the `button` action. ```ts copy title : name of the screen title @@ -238,6 +249,15 @@ button: "Rr" for the bot to push the Right button of the nano || "Ll" same for l expectedValue: string of what we want to compare ``` + + + The bot only supports Nano S Plus compatible device apps. Stax/Flex bot specs support to be included in the future.
+ To migrate from nanoS speculos deviceActions to nanoS+, execute the spec manually adapt the screen. +
+ ++ EXEMPLE + + You can use the following example to help you start to write how the bot will react : ```ts copy @@ -286,17 +306,16 @@ const acceptTransaction: DeviceAction = deviceActionFlow({ export default { acceptTransaction }; ``` -`specs.ts` - -You can check the following example to help you write your specs. +#### specs.ts -The bot will execute all the `mutations` if it doesn't encounter an invariant. +##### Mutation configuration -Then it will execute all the `updates` of the `Transaction` object. - -The bot will try to sign the transaction using instructions that you provided in `speculos-deviceActions.ts` +`name`: description of the mutation (will be included in the report) +`maxRun`: max number of executions of the spec itself +`transaction`: code to be executed for the spec +`test` : checks / expectations after the transaction execution -Eventually it will broadcast the transaction in the blockchain and wait for the `sync` to find the operation and its optimisic version. +##### Standard send/receive ```ts copy import expect from "expect"; @@ -362,6 +381,12 @@ const mycoin: AppSpec = { export default { mycoin }; ``` +##### Staking + + +##### Token support + + ### Optional parameters Some optional parameters can be configured in the bot spec. @@ -395,3 +420,16 @@ pnpm run:cli cleanSpeculos; SEED="generate a seed for testing" COINAPPS="/path/t - The bot will execute each scenario if it met the requirement, then it will wait until the sync find the broadcasted transaction - You also need to specify how the bot will react when he encounter certain screen, create `speculos-deviceActions.ts` + +### Common errors + +`⚠️ TEST waiting operation id to appear after broadcast` + +-> Operation that has been broadcasted, for some reason, after the timeout delay , does not end up in the history (timeout delay is not enough or operation gets lost in the process). + +`⚠️ Error: device action timeout. Recent events was:` + +An update is required on the screens: + + + From 68e0927c02fed5547e43be338ad21940e46fe23c Mon Sep 17 00:00:00 2001 From: cfranceschi-ledger Date: Fri, 6 Sep 2024 10:51:35 +0200 Subject: [PATCH 3/7] examples + errors --- pages/docs/blockchain/setup-build.mdx | 2 +- pages/docs/blockchain/testing.mdx | 431 +++++++++++++++++++++++--- 2 files changed, 389 insertions(+), 44 deletions(-) diff --git a/pages/docs/blockchain/setup-build.mdx b/pages/docs/blockchain/setup-build.mdx index 01b785d8..317a7530 100644 --- a/pages/docs/blockchain/setup-build.mdx +++ b/pages/docs/blockchain/setup-build.mdx @@ -98,7 +98,7 @@ Here is a typical family folder structure (TS integration): #### Coin-module -In the coin module, there is shared logic but blockchain specific, and how signer needs to be injected. +In the coin module, there is lockchain specific code, and how the signer needs to be injected. * `src/api` directory: Contains all the logic to send requests to the explorer. * `src/bridge/js.ts` file: Entry point of Ledger Live integration of the coin-module. This file lists `BridgeCurrency` and `BridgeAccount` implementation. diff --git a/pages/docs/blockchain/testing.mdx b/pages/docs/blockchain/testing.mdx index d8f1dba0..8e1c8edf 100644 --- a/pages/docs/blockchain/testing.mdx +++ b/pages/docs/blockchain/testing.mdx @@ -308,12 +308,18 @@ export default { acceptTransaction }; #### specs.ts + + You should force a specific Nano version only if mandatory, in general it is always better to let the bot target the latest version. + + +You can find the full list of available parameters [here](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/coin-framework/src/bot/types.ts) for AppSpec and MutationSpec. + ##### Mutation configuration -`name`: description of the mutation (will be included in the report) -`maxRun`: max number of executions of the spec itself -`transaction`: code to be executed for the spec -`test` : checks / expectations after the transaction execution +- `name`: description of the mutation (will be included in the report) +- `maxRun`: max number of executions of the spec itself +- `transaction`: code to be executed for the spec +- `test` : checks / expectations after the transaction execution ##### Standard send/receive @@ -347,33 +353,76 @@ const mycoin: AppSpec = { }, mutations: [ { - name: "move ~50%", - maxRun: 2, + name: "send max", + maxRun: 1, + testDestination: genericTestDestination, transaction: ({ account, siblings, bridge, maxSpendable }) => { + invariant(maxSpendable.gt(0), "Spendable balance is too low"); const sibling = pickSiblings(siblings, 4); - const recipient = sibling.freshAddress; - - let transaction = bridge.createTransaction(account); - - let amount = spendableBalance - .div(1.9 + 0.2 * Math.random()) - .integerValue(); - + // Send the full spendable balance + const amount = maxSpendable; checkSendableToEmptyAccount(amount, sibling); - - const updates = [{ amount }, { recipient }]; + return { + transaction: bridge.createTransaction(account), + updates: [ + { + recipient: sibling.freshAddress, + }, + { + useAllAmount: true, + }, + ], + }; + }, + test: ({ account }) => { + // Ensure that there is no more than 20 μALGOs (discretionary value) + // between the actual balance and the expected one to take into account + // the eventual pending rewards added _after_ the transaction + botTest("account spendable balance is very low", () => + expect(account.spendableBalance.lt(20)).toBe(true), + ); + }, + name: "send ASA ~50%", + maxRun: 2, + transaction: ({ account, siblings, bridge, maxSpendable }) => { + invariant(maxSpendable.gt(0), "Spendable balance is too low"); + const subAccount = sample(getAssetsWithBalance(account)); + invariant(subAccount && subAccount.type === "TokenAccount", "no subAccount with ASA"); + const assetId = subAccount.token.id; + const sibling = pickSiblingsOptedIn(siblings, assetId); + const transaction = bridge.createTransaction(account); + const recipient = (sibling as Account).freshAddress; + const mode = "send"; + const amount = subAccount.balance.div(1.9 + 0.2 * Math.random()).integerValue(); + const updates: Array> = [ + { + mode, + subAccountId: subAccount.id, + }, + { + recipient, + }, + { + amount, + }, + ]; return { transaction, updates, }; }, - test: ({ account, accountBeforeTransaction, operation }) => { - botTest("account balance decreased with operation value", () => - expect(account.balance.toString()).toBe( - accountBeforeTransaction.balance.minus(operation.value).toString() - ) + test: ({ account, accountBeforeTransaction, transaction, status }) => { + const subAccountId = transaction.subAccountId; + const subAccount = account.subAccounts?.find(sa => sa.id === subAccountId); + const subAccountBeforeTransaction = accountBeforeTransaction.subAccounts?.find( + sa => sa.id === subAccountId, ); - }, + botTest("subAccount balance moved with the tx status amount", () => + expect(subAccount?.balance.toString()).toBe( + subAccountBeforeTransaction?.balance.minus(status.amount).toString(), + ), + ); + } }, ], }; @@ -383,32 +432,237 @@ export default { mycoin }; ##### Staking +```ts copy + name: "delegate new validators", + maxRun: 1, + transaction: ({ account, bridge, siblings }) => { + expectSiblingsHaveSpendablePartGreaterThan(siblings, 0.5); + invariant(account.index % 2 > 0, "only one out of 2 accounts is not going to delegate"); + invariant(canDelegate(account as CosmosAccount), "can delegate"); + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + invariant( + (cosmosResources as CosmosResources).delegations.length < 3, + "already enough delegations", + ); + const data = getCurrentCosmosPreloadData()[account.currency.id]; + const count = 1; // we'r always going to have only one validator because of the new delegation flow. + let remaining = getMaxDelegationAvailable(account as CosmosAccount, count) + .minus(minimalTransactionAmount.times(2)) + .times(0.1 * Math.random()); + invariant(remaining.gt(0), "not enough funds in account for delegate"); + const all = data.validators.filter( + v => + !(cosmosResources as CosmosResources).delegations.some( + // new delegations only + d => d.validatorAddress === v.validatorAddress, + ), + ); + invariant(all.length > 0, "no validators found"); + const validators = sampleSize(all, count) + .map(delegation => { + // take a bit of remaining each time (less is preferred with the random() square) + const amount = remaining.times(Math.random() * Math.random()).integerValue(); + remaining = remaining.minus(amount); + return { + address: delegation.validatorAddress, + amount, + }; + }) + .filter(v => v.amount.gt(0)); + invariant(validators.length > 0, "no possible delegation found"); + return { + transaction: bridge.createTransaction(account), + updates: [ + { + memo: "LedgerLiveBot", + mode: "delegate", + }, + { + validators: validators, + }, + { amount: validators[0].amount }, + ], + }; + }, + test: ({ account, transaction }) => { + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + transaction.validators.forEach(v => { + const d = (cosmosResources as CosmosResources).delegations.find( + d => d.validatorAddress === v.address, + ); + invariant(d, "delegated %s must be found in account", v.address); + botTest("delegator have planned address and amount", () => { + expect(v.address).toBe((d as CosmosDelegation).validatorAddress); + checkAmountsCloseEnough(v.amount, (d as CosmosDelegation).amount); + }); + }); + }, + { + name: "undelegate", + maxRun: 5, + transaction: ({ account, bridge, maxSpendable }) => { + invariant(canUndelegate(account as CosmosAccount), "can undelegate"); + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + invariant(maxSpendable.gt(minimalTransactionAmount.times(2)), "balance is too low"); + invariant( + (cosmosResources as CosmosResources).delegations.length > 0, + "already enough delegations", + ); + const undelegateCandidate = sample( + (cosmosResources as CosmosResources).delegations.filter( + d => + !(cosmosResources as CosmosResources).redelegations.some( + r => + r.validatorSrcAddress === d.validatorAddress || + r.validatorDstAddress === d.validatorAddress, + ) && + !(cosmosResources as CosmosResources).unbondings.some( + r => r.validatorAddress === d.validatorAddress, + ), + ), + ); + invariant(undelegateCandidate, "already pending"); -##### Token support - - -### Optional parameters + const amount = (undelegateCandidate as CosmosDelegation).amount + .times(Math.random() > 0.2 ? 1 : Math.random()) // most of the time, undelegate all + .integerValue(); + invariant(amount.gt(0), "random amount to be positive"); -Some optional parameters can be configured in the bot spec. + return { + transaction: bridge.createTransaction(account), + updates: [ + { + mode: "undelegate", + memo: "LedgerLiveBot", + }, + { + validators: [ + { + address: (undelegateCandidate as CosmosDelegation).validatorAddress, + amount, + }, + ], + }, + ], + }; + }, + test: ({ account, transaction }) => { + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + transaction.validators.forEach(v => { + const d = (cosmosResources as CosmosResources).unbondings.find( + d => d.validatorAddress === v.address, + ); + invariant(d, "undelegated %s must be found in account", v.address); + botTest("validator have planned address and amount", () => { + expect(v.address).toBe((d as CosmosUnbonding).validatorAddress); + checkAmountsCloseEnough(v.amount, (d as CosmosUnbonding).amount); + }); + }); + }, + }, + name: "claim rewards", + maxRun: 1, + transaction: ({ account, bridge, maxSpendable }) => { + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + invariant( + maxSpendable.gt(minimalTransactionAmount.times(2)), + "balance is too low for claim rewards", + ); + const delegation = sample( + (cosmosResources as CosmosResources).delegations.filter(d => d.pendingRewards.gt(1000)), + ) as CosmosDelegation; + invariant(delegation, "no delegation to claim"); + return { + transaction: bridge.createTransaction(account), + updates: [ + { + mode: "claimReward", + memo: "LedgerLiveBot", + validators: [ + { + address: delegation.validatorAddress, + amount: delegation.pendingRewards, + }, + ], + }, + ], + }; + }, + test: ({ account, transaction }) => { + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + transaction.validators.forEach(v => { + const d = (cosmosResources as CosmosResources).delegations.find( + d => d.validatorAddress === v.address, + ); + botTest("delegation exists in account", () => + invariant(d, "delegation %s must be found in account", v.address), + ); + botTest("reward is no longer claimable after claim", () => + invariant( + d?.pendingRewards.lte(d.amount.multipliedBy(0.1)), + "pending reward is not reset", + ), + ); + }); + } +``` -For instance you can customize the embedded app version using the `appQuery` object: +#### Token support ```ts copy - // a query to select one embedded app. the most up to date version is selected when fields aren't set. - appQuery: { - model?: DeviceModelId; - appName?: string; - firmware?: string; - appVersion?: string; - }; + name: "move some ERC20 like (ERC20, BEP20, etc...)", + maxRun: 1, + testDestination: testTokenDestination, + transaction: ({ account, siblings, bridge }): TransactionRes => { + const erc20Account = sample((account.subAccounts || []).filter(a => a.balance.gt(0))); + invariant(erc20Account, "no erc20 account"); + const sibling = pickSiblings(siblings, 3); + const recipient = sibling.freshAddress; + return { + transaction: bridge.createTransaction(account), + updates: [ + { + recipient, + subAccountId: erc20Account!.id, + }, + Math.random() < 0.5 + ? { + useAllAmount: true, + } + : { + amount: erc20Account!.balance.times(Math.random()).integerValue(), + }, + ], + }; + }, + test: ({ accountBeforeTransaction, account, transaction }): void => { + invariant(accountBeforeTransaction.subAccounts, "sub accounts before"); + const erc20accountBefore = accountBeforeTransaction.subAccounts?.find( + s => s.id === transaction.subAccountId, + ); + invariant(erc20accountBefore, "erc20 acc was here before"); + invariant(account.subAccounts, "sub accounts"); + const erc20account = account.subAccounts!.find(s => s.id === transaction.subAccountId); + invariant(erc20account, "erc20 acc is still here"); + + if (transaction.useAllAmount) { + botTest("erc20 account is empty", () => expect(erc20account!.balance.toString()).toBe("0")); + } else { + botTest("account balance moved with tx amount", () => + expect(erc20account!.balance.toString()).toBe( + erc20accountBefore!.balance.minus(transaction.amount).toString(), + ), + ); + } + } ``` - - You should force a specific Nano version only if mandatory, in general it is always better to let the bot target the latest version. - - -You can find the full list of available parameters [here](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/coin-framework/src/bot/types.ts) for AppSpec and MutationSpec. - ### Launch the bot ```sh copy @@ -427,9 +681,100 @@ pnpm run:cli cleanSpeculos; SEED="generate a seed for testing" COINAPPS="/path/t -> Operation that has been broadcasted, for some reason, after the timeout delay , does not end up in the history (timeout delay is not enough or operation gets lost in the process). -`⚠️ Error: device action timeout. Recent events was:` +``` +⚠️ Error: device action timeout. Recent events was: +{"text":"TFAG598RAS3di4UiTE","x":7,"y":17,"w":115,"h":32} +{"text":"From Address (2/2)","x":16,"y":3,"w":106,"h":32} +{"text":"MtTEqGfWf7zptyiT","x":14,"y":17,"w":108,"h":32} +{"text":"To (1/2)","x":45,"y":3,"w":77,"h":32} +{"text":"TRtxvsTwcrabLwwE4","x":9,"y":17,"w":113,"h":32} +(totally spent 61.6s – ends at 2024-04-04T05:55:53.966Z) +``` -An update is required on the screens: +-> An update is required on the screens + +``` +necessary accounts resynced in 0.18ms +▬ Filecoin 0.24.1 on nanoSP 1.1.1 +→ FROM Filecoin 2 cross [bip44]: 0.616356 FIL (25ops) (f1a57ukatezm5aiuahhdrip3eyf7hzoj73u3ieyrq on 44'/461'/1'/0/0) filecoinBIP44#1 js:2:filecoin:f1a57ukatezm5aiuahhdrip3eyf7hzoj73u3ieyrq:filecoinBIP44 (! sum of ops 0.616356356693009588 FIL) +max spendable ~0.616356 +★ using mutation 'Transfer Max' +→ TO Filecoin 7 [bip44]: 0 FIL (6ops) (f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji on 44'/461'/6'/0/0) filecoinBIP44#6 js:2:filecoin:f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji:filecoinBIP44 +✔️ transaction +SEND MAX +TO f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji +STATUS (1435ms) + amount: 0.616356203626308647 FIL + estimated fees: 0.000000153066700962 FIL + total spent: 0.616356356693009609 FIL +errors: +errors: +⚠️ TEST deviceAction confirm step 'Value' +Error: expect(received).toMatchObject(expected) + +- Expected - 1 ++ Received + 1 + + Object { +- "Value": "0.616356203626308647", ++ "Value": "FIL 0.616356203626308647", + } +(totally spent 5.2s – ends at 2024-04-04T05:55:53.928Z) +``` +-> An update updated is required on the screens (new prefix) +``` +necessary accounts resynced in 0.24ms +▬ Cosmos 2.35.19 on nanoS 2.1.0 +→ FROM Cosmos 2: 0.034471 ATOM (0ops) (cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935 on 44'/118'/1'/0/0) #1 js:2:cosmos:cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935: (! sum of ops 0 ATOM) 0.034471 ATOM spendable. + +max spendable ~0.03276 +★ using mutation 'send max' +→ TO Cosmos 4: 0.02964 ATOM (0ops) (cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4 on 44'/118'/3'/0/0) #3 js:2:cosmos:cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4: +✔️ transaction +SEND MAX +TO cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4 + +with fees=0.001713 +STATUS (816ms) + amount: 0.032758 ATOM + estimated fees: 0.001713 ATOM + total spent: 0.034471 ATOM +errors: +errors: +✔️ has been signed! (7.9s) +✔️ broadcasted! (120ms) optimistic operation: + -0.034471 ATOM OUT 8893674CA6DD3A513111B253D8D3AB4150F83CF318297B197D12BB20238ABB99 2024-04-04T05:33 +⚠️ TEST waiting operation id to appear after broadcast +Error: could not find optimisticOperation js:2:cosmos:cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935:-8893674CA6DD3A513111B253D8D3AB4150F83CF318297B197D12BB20238ABB99-OUT +(totally spent 2min 9s – ends at 2024-04-04T05:55:53.870Z) +``` +-> Backend issue + +``` +necessary accounts resynced in 0.16ms +▬ Cosmos 2.35.19 on nanoS 2.1.0 +→ FROM Osmosis 7: 0.02492 OSMO (1ops) (osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng on 44'/118'/6'/0/0) #6 js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng: (! sum of ops -0.043349 OSMO) 0.02492 OSMO spendable. + +max spendable ~0.022518 +★ using mutation 'send some' +→ TO Osmosis 3: 0 OSMO (0ops) (osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv on 44'/118'/2'/0/0) #2 js:2:osmo:osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv: +✔️ transaction +SEND 0.007005 OSMO +TO osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv + +with fees=0.002006 + memo=LedgerLiveBot +STATUS (24.4s) + amount: 0.007005 OSMO + estimated fees: 0.002006 OSMO + total spent: 0.009011 OSMO +errors: +errors: +✔️ has been signed! (13.6s) {"operation":{"id":"js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng:--OUT","hash":"","type":"OUT","senders":["osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng"],"recipients":["osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv"],"accountId":"js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng:","blockHash":null,"blockHeight":null,"extra":{},"date":"2024-04-03T11:28:05.878Z","value":"9011","fee":"2006","transactionSequenceNumber":41},"signature":"0a9b010a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2b6f736d6f316e367663637061373778377879686e6b39386a7936676733726d676a6b617a7875796b326e67122b6f736d6f3176767a7763366c3377666461716139726e636578386b3275777470777a747377736d376b6b761a0d0a05756f736d6f120437303035120d4c65646765724c697665426f7412670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210311e0d6cbe3a70b35428e38731b044d68614832fc25e293894cf3e26459312a7012040a02087f182912130a0d0a05756f736d6f12043230303610daf2041a40161aa02d9a42a76549f266102998c6135c76923360ea2a92b8119d36bc067d276a0d6682390d1d8716f86410a0eb94f4bb612ece9f36d89d66cdcff87bd63f02"} +⚠️ TEST during broadcast +LedgerAPI5xx: API HTTP 504 +(totally spent 98s – ends at 2024-04-03T11:32:38.725Z) +``` \ No newline at end of file From d7203af1413bef941ea7fe54e77ddc263461165e Mon Sep 17 00:00:00 2001 From: cfranceschi-ledger Date: Tue, 10 Sep 2024 14:26:26 +0200 Subject: [PATCH 4/7] page creation --- pages/docs/blockchain/testing.mdx | 762 ------------------ pages/docs/blockchain/testing/_meta.json | 2 + pages/docs/blockchain/testing/bot.mdx | 568 +++++++++++++ .../blockchain/testing/testbridge-utility.mdx | 197 +++++ 4 files changed, 767 insertions(+), 762 deletions(-) create mode 100644 pages/docs/blockchain/testing/bot.mdx create mode 100644 pages/docs/blockchain/testing/testbridge-utility.mdx diff --git a/pages/docs/blockchain/testing.mdx b/pages/docs/blockchain/testing.mdx index 8e1c8edf..1774e19b 100644 --- a/pages/docs/blockchain/testing.mdx +++ b/pages/docs/blockchain/testing.mdx @@ -3,8 +3,6 @@ title: Writing tests for Ledger Live integration description: "The only tests needed are Send 50% and Send max. Together these cover all the functions required." --- -import { Callout } from 'nextra/components' - # Writing Tests ## Introduction @@ -17,764 +15,4 @@ The only tests needed are "Send 50%" and "Send max". Together these cover all th Before testing, make sure you have [set up the CLI](./setup-build/ledger-live-cli/). -## Writing bridge.integration.test.ts - -Any test that requires HTTP to work should be moved in an integration test using the file name convention `.integration.test.ts`. In the case of testing a coin integration, we usually name that file `bridge.integration.test.ts` and we recommend to use the `testBridge` utility. - -The `testBridge` utility takes a "DatasetTest" as a parameter which will conduct many kind of integration tests. - -First, create a `bridge.integration.test.ts` file and fill it with this empty template: - -```ts copy -import "../../__tests__/test-helpers/setup"; -import { testBridge } from "../../__tests__/test-helpers/bridge"; -import type { DatasetTest } from "../../types"; -import type { Transaction } from "./types"; - -const dataset: DatasetTest = { - implementations: ["js"], - currencies: { - mycoin: {}, - }, -}; -``` - -You can also generate it with a Ledger device, with a seed that you want to freeze (meaning you don't want to do anymore transaction with that seed, or you will need to regenerate the snapshot everytime) and execute in the CLI the command: - -```sh copy -pnpm run:cli generateTestScanAccounts -c mycoin -``` - -The expected output is: - -```ts copy -import "../../__tests__/test-helpers/setup"; -import { testBridge } from "../../__tests__/test-helpers/bridge"; -import type { CurrenciesData } from "../../../types"; -import type { Transaction } from "../types"; - -const dataset: CurrenciesData = { - scanAccounts: [ - { - name: "mycoin seed 1", - apdus: ` - => 100112344221c00002200000000000000000000000 - <= 213321ac21234122100000000000000000 - => 100112344221c00002200000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002210000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002220000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002230000800000008000000080 - `, - }, - ], -}; - -testBridge(dataset); -``` - -Just keep the part with the scanAccounts and put it the `mycoin` part : - -```ts copy -import "../../__tests__/test-helpers/setup"; -import { testBridge } from "../../__tests__/test-helpers/bridge"; -import type { DatasetTest } from "../../types"; -import type { Transaction } from "./types"; - -const dataset: DatasetTest = { - implementations: ["js"], - currencies: { - mycoin: { - scanAccounts: [ - { - name: "mycoin seed 1", - apdus: ` - => 100112344221c00002200000000000000000000000 - <= 213321ac21234122100000000000000000 - => 100112344221c00002200000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002210000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002220000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002230000800000008000000080 - `, - }, - ], - }, - }, -}; - -testBridge(dataset); -``` - -Then, get info on the accounts that you want to freeze, they will be used as references for our tests. -It should look something like this: - -```ts copy -import "../../__tests__/test-helpers/setup"; -import { testBridge } from "../../__tests__/test-helpers/bridge"; -import type { DatasetTest } from "../../types"; -import type { Transaction } from "./types"; - -const dataset: DatasetTest = { - implementations: ["js"], - currencies: { - mycoin: { - scanAccounts: [ - { - name: "mycoin seed 1", - apdus: ` - => 100112344221c00002200000000000000000000000 - <= 213321ac21234122100000000000000000 - => 100112344221c00002200000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002210000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002220000800000008000000080 - <= 213321ac21234122100000000000000000 - => 100112344221c00002230000800000008000000080 - `, - }, - ], - accounts: [ - { - raw: { - id: `js:2:mycoin:ADDR:`, - seedIdentifier: ADDR, - name: "MyCoin 1", - derivationMode: "", - index: 0, - freshAddress: ADDR, - freshAddressPath: "44'/354'/0'/0/0'", - freshAddresses: [], - blockHeight: 0, - operations: [], - pendingOperations: [], - currencyId: "mycoin", - unitMagnitude: 10, - lastSyncDate: "", - balance: "2111000", - }, - transactions: [ - // HERE WE WILL INSERT OUR test - ], - }, - ], - }, - }, -}; - -testBridge(dataset); -``` - -The `accounts` part is manual. This allow you to chose the best parameters for the situation you want to test. - -### How does a test work? - -The transaction tests simulate an object `Transaction` as input, and a `TransactionStatus` as an output that we compare with an expected status. - -There's some generic tests that are already made in `src/__tests__/test-helpers/bridge.ts` that are mandatory to pass. - -To implement your own test in `test-dataset.ts`, add an Object typed like this in the array of `transactions`: - -```ts copy -import Transaction from "./types"; - -type TestTransaction = { - name: string; - transaction: Transaction; - expectedStatus: { - amount: BigNumber; - errors: {}; - warnings: {}; - }; -}; -``` - -This `TestTransaction` uses as mainAccount the account that we have set before and then execute the command `getTransactionStatus` by using the `transaction` object as input. - -### Use regular Jest tests if you need more flexibility - -We tried to cover as many cases as possible that are in `getTransactionStatus`. - -You are free to define your own extra tests in `bridge.integration.test.ts` (or any other integration test file) for more advanced tests that would not be covered by the bridge generic tests. - -### Launch the test - -To launch the test do: - -```bash copy -pnpm common jest --runTestsByPath src/families//bridge.integration.test.ts -``` - -Replace `` with the name of your coin. - -## Testing the transaction broadcast with the bot - -Transaction broadcast is an exception, it is tested differently, by a tool that we call "the bot". See below. - -### Requirements - -- Docker -- An elf of the embedded app for Nano S (create a empty folder with like : `//` example `nanos/1.6.1/mycoin`. The build of your embedded app must have the following format: `app_VERSION.elf`, for example `app_1.2.3.elf`) -- Some currencies of the coin - -### What is this testing? - -We are testing the broadcast part and sync part. - -The bot executes each possible `mutation` on one account as long as it is possible and will stop if the `maxRun` is reached, or if it encounter an `invariant`. - -The bot will then move to the next account and execute the same actions, until it reaches the configured `maxAccount`. - -Then it will execute all the `updates` of the `Transaction` object. - -The bot will try to sign the transaction using instructions that you provided in `speculos-deviceActions.ts` - -Eventually it will broadcast the transaction in the blockchain and wait for the `sync` to find the operation and its optimisic version. - -### How to define a test - -#### speculos-deviceActions.ts - -It is required to know every screen that your device app contains, it will use the `title` of the screen then optionally check if the `expectedValue` of that screen is what it expects, then eventually execute the `button` action. - - -```ts copy -title : name of the screen title -button: "Rr" for the bot to push the Right button of the nano || "Ll" same for left || "LRlr" same but for both -expectedValue: string of what we want to compare -``` - - - - The bot only supports Nano S Plus compatible device apps. Stax/Flex bot specs support to be included in the future.
- To migrate from nanoS speculos deviceActions to nanoS+, execute the spec manually adapt the screen. -
- -+ EXEMPLE - - -You can use the following example to help you start to write how the bot will react : - -```ts copy -import type { DeviceAction } from "../../bot/types"; -import type { Transaction } from "./types"; -import { formatCurrencyUnit } from "../../currencies"; -import { deviceActionFlow } from "../../bot/specs"; - -const expectedAmount = ({ account, status }) => - formatCurrencyUnit(account.unit, status.amount, { - disableRounding: true, - }) + " MCN"; - -const acceptTransaction: DeviceAction = deviceActionFlow({ - steps: [ - { - title: "Starting Balance", - button: "Rr", - expectedValue: expectedAmount, - }, - { - title: "Send", - button: "Rr", - expectedValue: expectedAmount, - }, - { - title: "Fee", - button: "Rr", - expectedValue: ({ account, status }) => - formatCurrencyUnit(account.unit, status.estimatedFees, { - disableRounding: true, - }) + " XLM", - }, - { - title: "Destination", - button: "Rr", - expectedValue: ({ transaction }) => transaction.recipient, - }, - { - title: "Accept", - button: "LRlr", - }, - ], -}); - -export default { acceptTransaction }; -``` - -#### specs.ts - - - You should force a specific Nano version only if mandatory, in general it is always better to let the bot target the latest version. - - -You can find the full list of available parameters [here](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/coin-framework/src/bot/types.ts) for AppSpec and MutationSpec. - -##### Mutation configuration - -- `name`: description of the mutation (will be included in the report) -- `maxRun`: max number of executions of the spec itself -- `transaction`: code to be executed for the spec -- `test` : checks / expectations after the transaction execution - -##### Standard send/receive - -```ts copy -import expect from "expect"; -import invariant from "invariant"; -import type { AppSpec } from "../../bot/types"; -import type { Transaction } from "./types"; -import type { Account } from "../../types"; -import { pickSiblings, botTest } from "../../bot/specs"; -import { isAccountEmpty } from "../../account"; - -// Ensure that, when the recipient corresponds to an empty account, -// the amount to send is greater or equal to the required minimum -// balance for such a recipient -const checkSendableToEmptyAccount = (amount, recipient) => { - if (isAccountEmpty(recipient) && amount.lte(minBalanceNewAccount)) { - invariant( - amount.gt(minBalanceNewAccount), - "not enough funds to send to new account" - ); - } -}; - -const mycoin: AppSpec = { - name: "mycoin", - currency, - appQuery: { - model: "nanoS", - appName: "mycoin", - }, - mutations: [ - { - name: "send max", - maxRun: 1, - testDestination: genericTestDestination, - transaction: ({ account, siblings, bridge, maxSpendable }) => { - invariant(maxSpendable.gt(0), "Spendable balance is too low"); - const sibling = pickSiblings(siblings, 4); - // Send the full spendable balance - const amount = maxSpendable; - checkSendableToEmptyAccount(amount, sibling); - return { - transaction: bridge.createTransaction(account), - updates: [ - { - recipient: sibling.freshAddress, - }, - { - useAllAmount: true, - }, - ], - }; - }, - test: ({ account }) => { - // Ensure that there is no more than 20 μALGOs (discretionary value) - // between the actual balance and the expected one to take into account - // the eventual pending rewards added _after_ the transaction - botTest("account spendable balance is very low", () => - expect(account.spendableBalance.lt(20)).toBe(true), - ); - }, - name: "send ASA ~50%", - maxRun: 2, - transaction: ({ account, siblings, bridge, maxSpendable }) => { - invariant(maxSpendable.gt(0), "Spendable balance is too low"); - const subAccount = sample(getAssetsWithBalance(account)); - invariant(subAccount && subAccount.type === "TokenAccount", "no subAccount with ASA"); - const assetId = subAccount.token.id; - const sibling = pickSiblingsOptedIn(siblings, assetId); - const transaction = bridge.createTransaction(account); - const recipient = (sibling as Account).freshAddress; - const mode = "send"; - const amount = subAccount.balance.div(1.9 + 0.2 * Math.random()).integerValue(); - const updates: Array> = [ - { - mode, - subAccountId: subAccount.id, - }, - { - recipient, - }, - { - amount, - }, - ]; - return { - transaction, - updates, - }; - }, - test: ({ account, accountBeforeTransaction, transaction, status }) => { - const subAccountId = transaction.subAccountId; - const subAccount = account.subAccounts?.find(sa => sa.id === subAccountId); - const subAccountBeforeTransaction = accountBeforeTransaction.subAccounts?.find( - sa => sa.id === subAccountId, - ); - botTest("subAccount balance moved with the tx status amount", () => - expect(subAccount?.balance.toString()).toBe( - subAccountBeforeTransaction?.balance.minus(status.amount).toString(), - ), - ); - } - }, - ], -}; - -export default { mycoin }; -``` - -##### Staking - -```ts copy - name: "delegate new validators", - maxRun: 1, - transaction: ({ account, bridge, siblings }) => { - expectSiblingsHaveSpendablePartGreaterThan(siblings, 0.5); - invariant(account.index % 2 > 0, "only one out of 2 accounts is not going to delegate"); - invariant(canDelegate(account as CosmosAccount), "can delegate"); - const { cosmosResources } = account as CosmosAccount; - invariant(cosmosResources, "cosmos"); - invariant( - (cosmosResources as CosmosResources).delegations.length < 3, - "already enough delegations", - ); - const data = getCurrentCosmosPreloadData()[account.currency.id]; - const count = 1; // we'r always going to have only one validator because of the new delegation flow. - let remaining = getMaxDelegationAvailable(account as CosmosAccount, count) - .minus(minimalTransactionAmount.times(2)) - .times(0.1 * Math.random()); - invariant(remaining.gt(0), "not enough funds in account for delegate"); - const all = data.validators.filter( - v => - !(cosmosResources as CosmosResources).delegations.some( - // new delegations only - d => d.validatorAddress === v.validatorAddress, - ), - ); - invariant(all.length > 0, "no validators found"); - const validators = sampleSize(all, count) - .map(delegation => { - // take a bit of remaining each time (less is preferred with the random() square) - const amount = remaining.times(Math.random() * Math.random()).integerValue(); - remaining = remaining.minus(amount); - return { - address: delegation.validatorAddress, - amount, - }; - }) - .filter(v => v.amount.gt(0)); - invariant(validators.length > 0, "no possible delegation found"); - return { - transaction: bridge.createTransaction(account), - updates: [ - { - memo: "LedgerLiveBot", - mode: "delegate", - }, - { - validators: validators, - }, - { amount: validators[0].amount }, - ], - }; - }, - test: ({ account, transaction }) => { - const { cosmosResources } = account as CosmosAccount; - invariant(cosmosResources, "cosmos"); - transaction.validators.forEach(v => { - const d = (cosmosResources as CosmosResources).delegations.find( - d => d.validatorAddress === v.address, - ); - invariant(d, "delegated %s must be found in account", v.address); - botTest("delegator have planned address and amount", () => { - expect(v.address).toBe((d as CosmosDelegation).validatorAddress); - checkAmountsCloseEnough(v.amount, (d as CosmosDelegation).amount); - }); - }); - }, - { - name: "undelegate", - maxRun: 5, - transaction: ({ account, bridge, maxSpendable }) => { - invariant(canUndelegate(account as CosmosAccount), "can undelegate"); - const { cosmosResources } = account as CosmosAccount; - invariant(cosmosResources, "cosmos"); - invariant(maxSpendable.gt(minimalTransactionAmount.times(2)), "balance is too low"); - invariant( - (cosmosResources as CosmosResources).delegations.length > 0, - "already enough delegations", - ); - const undelegateCandidate = sample( - (cosmosResources as CosmosResources).delegations.filter( - d => - !(cosmosResources as CosmosResources).redelegations.some( - r => - r.validatorSrcAddress === d.validatorAddress || - r.validatorDstAddress === d.validatorAddress, - ) && - !(cosmosResources as CosmosResources).unbondings.some( - r => r.validatorAddress === d.validatorAddress, - ), - ), - ); - invariant(undelegateCandidate, "already pending"); - - const amount = (undelegateCandidate as CosmosDelegation).amount - .times(Math.random() > 0.2 ? 1 : Math.random()) // most of the time, undelegate all - .integerValue(); - invariant(amount.gt(0), "random amount to be positive"); - - return { - transaction: bridge.createTransaction(account), - updates: [ - { - mode: "undelegate", - memo: "LedgerLiveBot", - }, - { - validators: [ - { - address: (undelegateCandidate as CosmosDelegation).validatorAddress, - amount, - }, - ], - }, - ], - }; - }, - test: ({ account, transaction }) => { - const { cosmosResources } = account as CosmosAccount; - invariant(cosmosResources, "cosmos"); - transaction.validators.forEach(v => { - const d = (cosmosResources as CosmosResources).unbondings.find( - d => d.validatorAddress === v.address, - ); - invariant(d, "undelegated %s must be found in account", v.address); - botTest("validator have planned address and amount", () => { - expect(v.address).toBe((d as CosmosUnbonding).validatorAddress); - checkAmountsCloseEnough(v.amount, (d as CosmosUnbonding).amount); - }); - }); - }, - }, - name: "claim rewards", - maxRun: 1, - transaction: ({ account, bridge, maxSpendable }) => { - const { cosmosResources } = account as CosmosAccount; - invariant(cosmosResources, "cosmos"); - invariant( - maxSpendable.gt(minimalTransactionAmount.times(2)), - "balance is too low for claim rewards", - ); - const delegation = sample( - (cosmosResources as CosmosResources).delegations.filter(d => d.pendingRewards.gt(1000)), - ) as CosmosDelegation; - invariant(delegation, "no delegation to claim"); - return { - transaction: bridge.createTransaction(account), - updates: [ - { - mode: "claimReward", - memo: "LedgerLiveBot", - validators: [ - { - address: delegation.validatorAddress, - amount: delegation.pendingRewards, - }, - ], - }, - ], - }; - }, - test: ({ account, transaction }) => { - const { cosmosResources } = account as CosmosAccount; - invariant(cosmosResources, "cosmos"); - transaction.validators.forEach(v => { - const d = (cosmosResources as CosmosResources).delegations.find( - d => d.validatorAddress === v.address, - ); - botTest("delegation exists in account", () => - invariant(d, "delegation %s must be found in account", v.address), - ); - botTest("reward is no longer claimable after claim", () => - invariant( - d?.pendingRewards.lte(d.amount.multipliedBy(0.1)), - "pending reward is not reset", - ), - ); - }); - } -``` - -#### Token support - -```ts copy - name: "move some ERC20 like (ERC20, BEP20, etc...)", - maxRun: 1, - testDestination: testTokenDestination, - transaction: ({ account, siblings, bridge }): TransactionRes => { - const erc20Account = sample((account.subAccounts || []).filter(a => a.balance.gt(0))); - invariant(erc20Account, "no erc20 account"); - const sibling = pickSiblings(siblings, 3); - const recipient = sibling.freshAddress; - return { - transaction: bridge.createTransaction(account), - updates: [ - { - recipient, - subAccountId: erc20Account!.id, - }, - Math.random() < 0.5 - ? { - useAllAmount: true, - } - : { - amount: erc20Account!.balance.times(Math.random()).integerValue(), - }, - ], - }; - }, - test: ({ accountBeforeTransaction, account, transaction }): void => { - invariant(accountBeforeTransaction.subAccounts, "sub accounts before"); - const erc20accountBefore = accountBeforeTransaction.subAccounts?.find( - s => s.id === transaction.subAccountId, - ); - invariant(erc20accountBefore, "erc20 acc was here before"); - invariant(account.subAccounts, "sub accounts"); - const erc20account = account.subAccounts!.find(s => s.id === transaction.subAccountId); - invariant(erc20account, "erc20 acc is still here"); - - if (transaction.useAllAmount) { - botTest("erc20 account is empty", () => expect(erc20account!.balance.toString()).toBe("0")); - } else { - botTest("account balance moved with tx amount", () => - expect(erc20account!.balance.toString()).toBe( - erc20accountBefore!.balance.minus(transaction.amount).toString(), - ), - ); - } - } -``` - -### Launch the bot - -```sh copy -pnpm run:cli cleanSpeculos; SEED="generate a seed for testing" COINAPPS="/path/to/coin/apps/folder" pnpm run:cli bot -c mycoin -``` - -- Generate a 24 words SEED, [iancoleman.io/bip39/](https://iancoleman.io/bip39/). Use this seed for testing purpose only, then use the command before to have an adresse and send some currencies into it - -- The bot will execute each scenario if it met the requirement, then it will wait until the sync find the broadcasted transaction - -- You also need to specify how the bot will react when he encounter certain screen, create `speculos-deviceActions.ts` - -### Common errors - -`⚠️ TEST waiting operation id to appear after broadcast` - --> Operation that has been broadcasted, for some reason, after the timeout delay , does not end up in the history (timeout delay is not enough or operation gets lost in the process). - -``` -⚠️ Error: device action timeout. Recent events was: -{"text":"TFAG598RAS3di4UiTE","x":7,"y":17,"w":115,"h":32} -{"text":"From Address (2/2)","x":16,"y":3,"w":106,"h":32} -{"text":"MtTEqGfWf7zptyiT","x":14,"y":17,"w":108,"h":32} -{"text":"To (1/2)","x":45,"y":3,"w":77,"h":32} -{"text":"TRtxvsTwcrabLwwE4","x":9,"y":17,"w":113,"h":32} -(totally spent 61.6s – ends at 2024-04-04T05:55:53.966Z) -``` - --> An update is required on the screens - -``` -necessary accounts resynced in 0.18ms -▬ Filecoin 0.24.1 on nanoSP 1.1.1 -→ FROM Filecoin 2 cross [bip44]: 0.616356 FIL (25ops) (f1a57ukatezm5aiuahhdrip3eyf7hzoj73u3ieyrq on 44'/461'/1'/0/0) filecoinBIP44#1 js:2:filecoin:f1a57ukatezm5aiuahhdrip3eyf7hzoj73u3ieyrq:filecoinBIP44 (! sum of ops 0.616356356693009588 FIL) -max spendable ~0.616356 -★ using mutation 'Transfer Max' -→ TO Filecoin 7 [bip44]: 0 FIL (6ops) (f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji on 44'/461'/6'/0/0) filecoinBIP44#6 js:2:filecoin:f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji:filecoinBIP44 -✔️ transaction -SEND MAX -TO f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji -STATUS (1435ms) - amount: 0.616356203626308647 FIL - estimated fees: 0.000000153066700962 FIL - total spent: 0.616356356693009609 FIL -errors: -errors: -⚠️ TEST deviceAction confirm step 'Value' -Error: expect(received).toMatchObject(expected) - -- Expected - 1 -+ Received + 1 - - Object { -- "Value": "0.616356203626308647", -+ "Value": "FIL 0.616356203626308647", - } -(totally spent 5.2s – ends at 2024-04-04T05:55:53.928Z) -``` - --> An update updated is required on the screens (new prefix) - -``` -necessary accounts resynced in 0.24ms -▬ Cosmos 2.35.19 on nanoS 2.1.0 -→ FROM Cosmos 2: 0.034471 ATOM (0ops) (cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935 on 44'/118'/1'/0/0) #1 js:2:cosmos:cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935: (! sum of ops 0 ATOM) 0.034471 ATOM spendable. - -max spendable ~0.03276 -★ using mutation 'send max' -→ TO Cosmos 4: 0.02964 ATOM (0ops) (cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4 on 44'/118'/3'/0/0) #3 js:2:cosmos:cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4: -✔️ transaction -SEND MAX -TO cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4 - -with fees=0.001713 -STATUS (816ms) - amount: 0.032758 ATOM - estimated fees: 0.001713 ATOM - total spent: 0.034471 ATOM -errors: -errors: -✔️ has been signed! (7.9s) -✔️ broadcasted! (120ms) optimistic operation: - -0.034471 ATOM OUT 8893674CA6DD3A513111B253D8D3AB4150F83CF318297B197D12BB20238ABB99 2024-04-04T05:33 -⚠️ TEST waiting operation id to appear after broadcast -Error: could not find optimisticOperation js:2:cosmos:cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935:-8893674CA6DD3A513111B253D8D3AB4150F83CF318297B197D12BB20238ABB99-OUT -(totally spent 2min 9s – ends at 2024-04-04T05:55:53.870Z) -``` - --> Backend issue - -``` -necessary accounts resynced in 0.16ms -▬ Cosmos 2.35.19 on nanoS 2.1.0 -→ FROM Osmosis 7: 0.02492 OSMO (1ops) (osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng on 44'/118'/6'/0/0) #6 js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng: (! sum of ops -0.043349 OSMO) 0.02492 OSMO spendable. - -max spendable ~0.022518 -★ using mutation 'send some' -→ TO Osmosis 3: 0 OSMO (0ops) (osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv on 44'/118'/2'/0/0) #2 js:2:osmo:osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv: -✔️ transaction -SEND 0.007005 OSMO -TO osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv -with fees=0.002006 - memo=LedgerLiveBot -STATUS (24.4s) - amount: 0.007005 OSMO - estimated fees: 0.002006 OSMO - total spent: 0.009011 OSMO -errors: -errors: -✔️ has been signed! (13.6s) {"operation":{"id":"js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng:--OUT","hash":"","type":"OUT","senders":["osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng"],"recipients":["osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv"],"accountId":"js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng:","blockHash":null,"blockHeight":null,"extra":{},"date":"2024-04-03T11:28:05.878Z","value":"9011","fee":"2006","transactionSequenceNumber":41},"signature":"0a9b010a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2b6f736d6f316e367663637061373778377879686e6b39386a7936676733726d676a6b617a7875796b326e67122b6f736d6f3176767a7763366c3377666461716139726e636578386b3275777470777a747377736d376b6b761a0d0a05756f736d6f120437303035120d4c65646765724c697665426f7412670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210311e0d6cbe3a70b35428e38731b044d68614832fc25e293894cf3e26459312a7012040a02087f182912130a0d0a05756f736d6f12043230303610daf2041a40161aa02d9a42a76549f266102998c6135c76923360ea2a92b8119d36bc067d276a0d6682390d1d8716f86410a0eb94f4bb612ece9f36d89d66cdcff87bd63f02"} -⚠️ TEST during broadcast -LedgerAPI5xx: API HTTP 504 -(totally spent 98s – ends at 2024-04-03T11:32:38.725Z) -``` \ No newline at end of file diff --git a/pages/docs/blockchain/testing/_meta.json b/pages/docs/blockchain/testing/_meta.json index a8c8e3c9..58be0e41 100644 --- a/pages/docs/blockchain/testing/_meta.json +++ b/pages/docs/blockchain/testing/_meta.json @@ -1,4 +1,6 @@ { + "testbridge-utility": "The testBridge utility", + "bot": "Testing with the bot", "bridge-test": "Live Common Bridge Test", "generic-test-plan": "Generic test plan", "ui-tests": "E2E UI Tests on Desktop and Mobile" diff --git a/pages/docs/blockchain/testing/bot.mdx b/pages/docs/blockchain/testing/bot.mdx new file mode 100644 index 00000000..b1f1fd25 --- /dev/null +++ b/pages/docs/blockchain/testing/bot.mdx @@ -0,0 +1,568 @@ +import { Callout } from 'nextra/components' + +# Testing the transaction broadcast with the bot + +Transaction broadcast is an exception, it is tested differently, by a tool that we call "the bot". See below. + +## Requirements + +- Docker +- An elf of the embedded app for Nano S (create a empty folder with like : `//` example `nanos/1.6.1/mycoin`. The build of your embedded app must have the following format: `app_VERSION.elf`, for example `app_1.2.3.elf`) +- Some currencies of the coin + +## What is this testing? + +We are testing the broadcast part and sync part. + +The bot executes each possible `mutation` on one account as long as it is possible and will stop if the `maxRun` is reached, or if it encounter an `invariant`. + +The bot will then move to the next account and execute the same actions, until it reaches the configured `maxAccount`. + +Then it will execute all the `updates` of the `Transaction` object. + +The bot will try to sign the transaction using instructions that you provided in `speculos-deviceActions.ts` + +Eventually it will broadcast the transaction in the blockchain and wait for the `sync` to find the operation and its optimisic version. + +## How to define a test + +### speculos-deviceActions.ts + +It is required to know every screen that your device app contains, it will use the `title` of the screen then optionally check if the `expectedValue` of that screen is what it expects, then eventually execute the `button` action. + + +```ts copy +title : name of the screen title +button: "Rr" for the bot to push the Right button of the nano || "Ll" same for left || "LRlr" same but for both +expectedValue: string of what we want to compare +``` + + + + The bot only supports Nano S Plus compatible device apps. Stax/Flex bot specs support to be included in the future.
+ To migrate from nanoS speculos deviceActions to nanoS+, execute the spec manually adapt the screen. +
+ ++ EXEMPLE + + +You can use the following example to help you start to write how the bot will react : + +```ts copy +import type { DeviceAction } from "../../bot/types"; +import type { Transaction } from "./types"; +import { formatCurrencyUnit } from "../../currencies"; +import { deviceActionFlow } from "../../bot/specs"; + +const expectedAmount = ({ account, status }) => + formatCurrencyUnit(account.unit, status.amount, { + disableRounding: true, + }) + " MCN"; + +const acceptTransaction: DeviceAction = deviceActionFlow({ + steps: [ + { + title: "Starting Balance", + button: "Rr", + expectedValue: expectedAmount, + }, + { + title: "Send", + button: "Rr", + expectedValue: expectedAmount, + }, + { + title: "Fee", + button: "Rr", + expectedValue: ({ account, status }) => + formatCurrencyUnit(account.unit, status.estimatedFees, { + disableRounding: true, + }) + " XLM", + }, + { + title: "Destination", + button: "Rr", + expectedValue: ({ transaction }) => transaction.recipient, + }, + { + title: "Accept", + button: "LRlr", + }, + ], +}); + +export default { acceptTransaction }; +``` + +### specs.ts + + + You should force a specific Nano version only if mandatory, in general it is always better to let the bot target the latest version. + + +You can find the full list of available parameters [here](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/coin-framework/src/bot/types.ts) for AppSpec and MutationSpec. + +#### Mutation configuration + +- `name`: description of the mutation (will be included in the report) +- `maxRun`: max number of executions of the spec itself +- `transaction`: code to be executed for the spec +- `test` : checks / expectations after the transaction execution + +#### Standard send/receive + +```ts copy +import expect from "expect"; +import invariant from "invariant"; +import type { AppSpec } from "../../bot/types"; +import type { Transaction } from "./types"; +import type { Account } from "../../types"; +import { pickSiblings, botTest } from "../../bot/specs"; +import { isAccountEmpty } from "../../account"; + +// Ensure that, when the recipient corresponds to an empty account, +// the amount to send is greater or equal to the required minimum +// balance for such a recipient +const checkSendableToEmptyAccount = (amount, recipient) => { + if (isAccountEmpty(recipient) && amount.lte(minBalanceNewAccount)) { + invariant( + amount.gt(minBalanceNewAccount), + "not enough funds to send to new account" + ); + } +}; + +const mycoin: AppSpec = { + name: "mycoin", + currency, + appQuery: { + model: "nanoS", + appName: "mycoin", + }, + mutations: [ + { + name: "send max", + maxRun: 1, + testDestination: genericTestDestination, + transaction: ({ account, siblings, bridge, maxSpendable }) => { + invariant(maxSpendable.gt(0), "Spendable balance is too low"); + const sibling = pickSiblings(siblings, 4); + // Send the full spendable balance + const amount = maxSpendable; + checkSendableToEmptyAccount(amount, sibling); + return { + transaction: bridge.createTransaction(account), + updates: [ + { + recipient: sibling.freshAddress, + }, + { + useAllAmount: true, + }, + ], + }; + }, + test: ({ account }) => { + // Ensure that there is no more than 20 μALGOs (discretionary value) + // between the actual balance and the expected one to take into account + // the eventual pending rewards added _after_ the transaction + botTest("account spendable balance is very low", () => + expect(account.spendableBalance.lt(20)).toBe(true), + ); + }, + name: "send ASA ~50%", + maxRun: 2, + transaction: ({ account, siblings, bridge, maxSpendable }) => { + invariant(maxSpendable.gt(0), "Spendable balance is too low"); + const subAccount = sample(getAssetsWithBalance(account)); + invariant(subAccount && subAccount.type === "TokenAccount", "no subAccount with ASA"); + const assetId = subAccount.token.id; + const sibling = pickSiblingsOptedIn(siblings, assetId); + const transaction = bridge.createTransaction(account); + const recipient = (sibling as Account).freshAddress; + const mode = "send"; + const amount = subAccount.balance.div(1.9 + 0.2 * Math.random()).integerValue(); + const updates: Array> = [ + { + mode, + subAccountId: subAccount.id, + }, + { + recipient, + }, + { + amount, + }, + ]; + return { + transaction, + updates, + }; + }, + test: ({ account, accountBeforeTransaction, transaction, status }) => { + const subAccountId = transaction.subAccountId; + const subAccount = account.subAccounts?.find(sa => sa.id === subAccountId); + const subAccountBeforeTransaction = accountBeforeTransaction.subAccounts?.find( + sa => sa.id === subAccountId, + ); + botTest("subAccount balance moved with the tx status amount", () => + expect(subAccount?.balance.toString()).toBe( + subAccountBeforeTransaction?.balance.minus(status.amount).toString(), + ), + ); + } + }, + ], +}; + +export default { mycoin }; +``` + +#### Staking + +```ts copy + name: "delegate new validators", + maxRun: 1, + transaction: ({ account, bridge, siblings }) => { + expectSiblingsHaveSpendablePartGreaterThan(siblings, 0.5); + invariant(account.index % 2 > 0, "only one out of 2 accounts is not going to delegate"); + invariant(canDelegate(account as CosmosAccount), "can delegate"); + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + invariant( + (cosmosResources as CosmosResources).delegations.length < 3, + "already enough delegations", + ); + const data = getCurrentCosmosPreloadData()[account.currency.id]; + const count = 1; // we'r always going to have only one validator because of the new delegation flow. + let remaining = getMaxDelegationAvailable(account as CosmosAccount, count) + .minus(minimalTransactionAmount.times(2)) + .times(0.1 * Math.random()); + invariant(remaining.gt(0), "not enough funds in account for delegate"); + const all = data.validators.filter( + v => + !(cosmosResources as CosmosResources).delegations.some( + // new delegations only + d => d.validatorAddress === v.validatorAddress, + ), + ); + invariant(all.length > 0, "no validators found"); + const validators = sampleSize(all, count) + .map(delegation => { + // take a bit of remaining each time (less is preferred with the random() square) + const amount = remaining.times(Math.random() * Math.random()).integerValue(); + remaining = remaining.minus(amount); + return { + address: delegation.validatorAddress, + amount, + }; + }) + .filter(v => v.amount.gt(0)); + invariant(validators.length > 0, "no possible delegation found"); + return { + transaction: bridge.createTransaction(account), + updates: [ + { + memo: "LedgerLiveBot", + mode: "delegate", + }, + { + validators: validators, + }, + { amount: validators[0].amount }, + ], + }; + }, + test: ({ account, transaction }) => { + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + transaction.validators.forEach(v => { + const d = (cosmosResources as CosmosResources).delegations.find( + d => d.validatorAddress === v.address, + ); + invariant(d, "delegated %s must be found in account", v.address); + botTest("delegator have planned address and amount", () => { + expect(v.address).toBe((d as CosmosDelegation).validatorAddress); + checkAmountsCloseEnough(v.amount, (d as CosmosDelegation).amount); + }); + }); + }, + { + name: "undelegate", + maxRun: 5, + transaction: ({ account, bridge, maxSpendable }) => { + invariant(canUndelegate(account as CosmosAccount), "can undelegate"); + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + invariant(maxSpendable.gt(minimalTransactionAmount.times(2)), "balance is too low"); + invariant( + (cosmosResources as CosmosResources).delegations.length > 0, + "already enough delegations", + ); + const undelegateCandidate = sample( + (cosmosResources as CosmosResources).delegations.filter( + d => + !(cosmosResources as CosmosResources).redelegations.some( + r => + r.validatorSrcAddress === d.validatorAddress || + r.validatorDstAddress === d.validatorAddress, + ) && + !(cosmosResources as CosmosResources).unbondings.some( + r => r.validatorAddress === d.validatorAddress, + ), + ), + ); + invariant(undelegateCandidate, "already pending"); + + const amount = (undelegateCandidate as CosmosDelegation).amount + .times(Math.random() > 0.2 ? 1 : Math.random()) // most of the time, undelegate all + .integerValue(); + invariant(amount.gt(0), "random amount to be positive"); + + return { + transaction: bridge.createTransaction(account), + updates: [ + { + mode: "undelegate", + memo: "LedgerLiveBot", + }, + { + validators: [ + { + address: (undelegateCandidate as CosmosDelegation).validatorAddress, + amount, + }, + ], + }, + ], + }; + }, + test: ({ account, transaction }) => { + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + transaction.validators.forEach(v => { + const d = (cosmosResources as CosmosResources).unbondings.find( + d => d.validatorAddress === v.address, + ); + invariant(d, "undelegated %s must be found in account", v.address); + botTest("validator have planned address and amount", () => { + expect(v.address).toBe((d as CosmosUnbonding).validatorAddress); + checkAmountsCloseEnough(v.amount, (d as CosmosUnbonding).amount); + }); + }); + }, + }, + name: "claim rewards", + maxRun: 1, + transaction: ({ account, bridge, maxSpendable }) => { + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + invariant( + maxSpendable.gt(minimalTransactionAmount.times(2)), + "balance is too low for claim rewards", + ); + const delegation = sample( + (cosmosResources as CosmosResources).delegations.filter(d => d.pendingRewards.gt(1000)), + ) as CosmosDelegation; + invariant(delegation, "no delegation to claim"); + return { + transaction: bridge.createTransaction(account), + updates: [ + { + mode: "claimReward", + memo: "LedgerLiveBot", + validators: [ + { + address: delegation.validatorAddress, + amount: delegation.pendingRewards, + }, + ], + }, + ], + }; + }, + test: ({ account, transaction }) => { + const { cosmosResources } = account as CosmosAccount; + invariant(cosmosResources, "cosmos"); + transaction.validators.forEach(v => { + const d = (cosmosResources as CosmosResources).delegations.find( + d => d.validatorAddress === v.address, + ); + botTest("delegation exists in account", () => + invariant(d, "delegation %s must be found in account", v.address), + ); + botTest("reward is no longer claimable after claim", () => + invariant( + d?.pendingRewards.lte(d.amount.multipliedBy(0.1)), + "pending reward is not reset", + ), + ); + }); + } +``` + +### Token support + +```ts copy + name: "move some ERC20 like (ERC20, BEP20, etc...)", + maxRun: 1, + testDestination: testTokenDestination, + transaction: ({ account, siblings, bridge }): TransactionRes => { + const erc20Account = sample((account.subAccounts || []).filter(a => a.balance.gt(0))); + invariant(erc20Account, "no erc20 account"); + const sibling = pickSiblings(siblings, 3); + const recipient = sibling.freshAddress; + return { + transaction: bridge.createTransaction(account), + updates: [ + { + recipient, + subAccountId: erc20Account!.id, + }, + Math.random() < 0.5 + ? { + useAllAmount: true, + } + : { + amount: erc20Account!.balance.times(Math.random()).integerValue(), + }, + ], + }; + }, + test: ({ accountBeforeTransaction, account, transaction }): void => { + invariant(accountBeforeTransaction.subAccounts, "sub accounts before"); + const erc20accountBefore = accountBeforeTransaction.subAccounts?.find( + s => s.id === transaction.subAccountId, + ); + invariant(erc20accountBefore, "erc20 acc was here before"); + invariant(account.subAccounts, "sub accounts"); + const erc20account = account.subAccounts!.find(s => s.id === transaction.subAccountId); + invariant(erc20account, "erc20 acc is still here"); + + if (transaction.useAllAmount) { + botTest("erc20 account is empty", () => expect(erc20account!.balance.toString()).toBe("0")); + } else { + botTest("account balance moved with tx amount", () => + expect(erc20account!.balance.toString()).toBe( + erc20accountBefore!.balance.minus(transaction.amount).toString(), + ), + ); + } + } +``` + +## Launch the bot + +```sh copy +pnpm run:cli cleanSpeculos; SEED="generate a seed for testing" COINAPPS="/path/to/coin/apps/folder" pnpm run:cli bot -c mycoin +``` + +- Generate a 24 words SEED, [iancoleman.io/bip39/](https://iancoleman.io/bip39/). Use this seed for testing purpose only, then use the command before to have an adresse and send some currencies into it + +- The bot will execute each scenario if it met the requirement, then it will wait until the sync find the broadcasted transaction + +- You also need to specify how the bot will react when he encounter certain screen, create `speculos-deviceActions.ts` + +## Common errors + +`⚠️ TEST waiting operation id to appear after broadcast` + +-> Operation that has been broadcasted, for some reason, after the timeout delay , does not end up in the history (timeout delay is not enough or operation gets lost in the process). + +``` +⚠️ Error: device action timeout. Recent events was: +{"text":"TFAG598RAS3di4UiTE","x":7,"y":17,"w":115,"h":32} +{"text":"From Address (2/2)","x":16,"y":3,"w":106,"h":32} +{"text":"MtTEqGfWf7zptyiT","x":14,"y":17,"w":108,"h":32} +{"text":"To (1/2)","x":45,"y":3,"w":77,"h":32} +{"text":"TRtxvsTwcrabLwwE4","x":9,"y":17,"w":113,"h":32} +(totally spent 61.6s – ends at 2024-04-04T05:55:53.966Z) +``` + +-> An update is required on the screens + +``` +necessary accounts resynced in 0.18ms +▬ Filecoin 0.24.1 on nanoSP 1.1.1 +→ FROM Filecoin 2 cross [bip44]: 0.616356 FIL (25ops) (f1a57ukatezm5aiuahhdrip3eyf7hzoj73u3ieyrq on 44'/461'/1'/0/0) filecoinBIP44#1 js:2:filecoin:f1a57ukatezm5aiuahhdrip3eyf7hzoj73u3ieyrq:filecoinBIP44 (! sum of ops 0.616356356693009588 FIL) +max spendable ~0.616356 +★ using mutation 'Transfer Max' +→ TO Filecoin 7 [bip44]: 0 FIL (6ops) (f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji on 44'/461'/6'/0/0) filecoinBIP44#6 js:2:filecoin:f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji:filecoinBIP44 +✔️ transaction +SEND MAX +TO f1bw4zwsefo5rwk3t536tq6wlgdhivrt5ucmdttji +STATUS (1435ms) + amount: 0.616356203626308647 FIL + estimated fees: 0.000000153066700962 FIL + total spent: 0.616356356693009609 FIL +errors: +errors: +⚠️ TEST deviceAction confirm step 'Value' +Error: expect(received).toMatchObject(expected) + +- Expected - 1 ++ Received + 1 + + Object { +- "Value": "0.616356203626308647", ++ "Value": "FIL 0.616356203626308647", + } +(totally spent 5.2s – ends at 2024-04-04T05:55:53.928Z) +``` + +-> An update updated is required on the screens (new prefix) + +``` +necessary accounts resynced in 0.24ms +▬ Cosmos 2.35.19 on nanoS 2.1.0 +→ FROM Cosmos 2: 0.034471 ATOM (0ops) (cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935 on 44'/118'/1'/0/0) #1 js:2:cosmos:cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935: (! sum of ops 0 ATOM) 0.034471 ATOM spendable. + +max spendable ~0.03276 +★ using mutation 'send max' +→ TO Cosmos 4: 0.02964 ATOM (0ops) (cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4 on 44'/118'/3'/0/0) #3 js:2:cosmos:cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4: +✔️ transaction +SEND MAX +TO cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4 + +with fees=0.001713 +STATUS (816ms) + amount: 0.032758 ATOM + estimated fees: 0.001713 ATOM + total spent: 0.034471 ATOM +errors: +errors: +✔️ has been signed! (7.9s) +✔️ broadcasted! (120ms) optimistic operation: + -0.034471 ATOM OUT 8893674CA6DD3A513111B253D8D3AB4150F83CF318297B197D12BB20238ABB99 2024-04-04T05:33 +⚠️ TEST waiting operation id to appear after broadcast +Error: could not find optimisticOperation js:2:cosmos:cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935:-8893674CA6DD3A513111B253D8D3AB4150F83CF318297B197D12BB20238ABB99-OUT +(totally spent 2min 9s – ends at 2024-04-04T05:55:53.870Z) +``` + +-> Backend issue + +``` +necessary accounts resynced in 0.16ms +▬ Cosmos 2.35.19 on nanoS 2.1.0 +→ FROM Osmosis 7: 0.02492 OSMO (1ops) (osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng on 44'/118'/6'/0/0) #6 js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng: (! sum of ops -0.043349 OSMO) 0.02492 OSMO spendable. + +max spendable ~0.022518 +★ using mutation 'send some' +→ TO Osmosis 3: 0 OSMO (0ops) (osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv on 44'/118'/2'/0/0) #2 js:2:osmo:osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv: +✔️ transaction +SEND 0.007005 OSMO +TO osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv + +with fees=0.002006 + memo=LedgerLiveBot +STATUS (24.4s) + amount: 0.007005 OSMO + estimated fees: 0.002006 OSMO + total spent: 0.009011 OSMO +errors: +errors: +✔️ has been signed! (13.6s) {"operation":{"id":"js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng:--OUT","hash":"","type":"OUT","senders":["osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng"],"recipients":["osmo1vvzwc6l3wfdaqa9rncex8k2uwtpwztswsm7kkv"],"accountId":"js:2:osmo:osmo1n6vccpa77x7xyhnk98jy6gg3rmgjkazxuyk2ng:","blockHash":null,"blockHeight":null,"extra":{},"date":"2024-04-03T11:28:05.878Z","value":"9011","fee":"2006","transactionSequenceNumber":41},"signature":"0a9b010a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2b6f736d6f316e367663637061373778377879686e6b39386a7936676733726d676a6b617a7875796b326e67122b6f736d6f3176767a7763366c3377666461716139726e636578386b3275777470777a747377736d376b6b761a0d0a05756f736d6f120437303035120d4c65646765724c697665426f7412670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210311e0d6cbe3a70b35428e38731b044d68614832fc25e293894cf3e26459312a7012040a02087f182912130a0d0a05756f736d6f12043230303610daf2041a40161aa02d9a42a76549f266102998c6135c76923360ea2a92b8119d36bc067d276a0d6682390d1d8716f86410a0eb94f4bb612ece9f36d89d66cdcff87bd63f02"} +⚠️ TEST during broadcast +LedgerAPI5xx: API HTTP 504 +(totally spent 98s – ends at 2024-04-03T11:32:38.725Z) +``` \ No newline at end of file diff --git a/pages/docs/blockchain/testing/testbridge-utility.mdx b/pages/docs/blockchain/testing/testbridge-utility.mdx new file mode 100644 index 00000000..382b22b7 --- /dev/null +++ b/pages/docs/blockchain/testing/testbridge-utility.mdx @@ -0,0 +1,197 @@ +# Using the `testBridge` utility + +## Write the bridge.integration.test.ts + +Any test that requires HTTP to work should be moved in an integration test using the file name convention `.integration.test.ts`. In the case of testing a coin integration, we usually name that file `bridge.integration.test.ts` and we recommend to use the `testBridge` utility. + +The `testBridge` utility takes a "DatasetTest" as a parameter which will conduct many kind of integration tests. + +First, create a `bridge.integration.test.ts` file and fill it with this empty template: + +```ts copy +import "../../__tests__/test-helpers/setup"; +import { testBridge } from "../../__tests__/test-helpers/bridge"; +import type { DatasetTest } from "../../types"; +import type { Transaction } from "./types"; + +const dataset: DatasetTest = { + implementations: ["js"], + currencies: { + mycoin: {}, + }, +}; +``` + +You can also generate it with a Ledger device, with a seed that you want to freeze (meaning you don't want to do anymore transaction with that seed, or you will need to regenerate the snapshot everytime) and execute in the CLI the command: + +```sh copy +pnpm run:cli generateTestScanAccounts -c mycoin +``` + +The expected output is: + +```ts copy +import "../../__tests__/test-helpers/setup"; +import { testBridge } from "../../__tests__/test-helpers/bridge"; +import type { CurrenciesData } from "../../../types"; +import type { Transaction } from "../types"; + +const dataset: CurrenciesData = { + scanAccounts: [ + { + name: "mycoin seed 1", + apdus: ` + => 100112344221c00002200000000000000000000000 + <= 213321ac21234122100000000000000000 + => 100112344221c00002200000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002210000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002220000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002230000800000008000000080 + `, + }, + ], +}; + +testBridge(dataset); +``` + +Just keep the part with the scanAccounts and put it the `mycoin` part : + +```ts copy +import "../../__tests__/test-helpers/setup"; +import { testBridge } from "../../__tests__/test-helpers/bridge"; +import type { DatasetTest } from "../../types"; +import type { Transaction } from "./types"; + +const dataset: DatasetTest = { + implementations: ["js"], + currencies: { + mycoin: { + scanAccounts: [ + { + name: "mycoin seed 1", + apdus: ` + => 100112344221c00002200000000000000000000000 + <= 213321ac21234122100000000000000000 + => 100112344221c00002200000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002210000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002220000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002230000800000008000000080 + `, + }, + ], + }, + }, +}; + +testBridge(dataset); +``` + +Then, get info on the accounts that you want to freeze, they will be used as references for our tests. +It should look something like this: + +```ts copy +import "../../__tests__/test-helpers/setup"; +import { testBridge } from "../../__tests__/test-helpers/bridge"; +import type { DatasetTest } from "../../types"; +import type { Transaction } from "./types"; + +const dataset: DatasetTest = { + implementations: ["js"], + currencies: { + mycoin: { + scanAccounts: [ + { + name: "mycoin seed 1", + apdus: ` + => 100112344221c00002200000000000000000000000 + <= 213321ac21234122100000000000000000 + => 100112344221c00002200000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002210000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002220000800000008000000080 + <= 213321ac21234122100000000000000000 + => 100112344221c00002230000800000008000000080 + `, + }, + ], + accounts: [ + { + raw: { + id: `js:2:mycoin:ADDR:`, + seedIdentifier: ADDR, + name: "MyCoin 1", + derivationMode: "", + index: 0, + freshAddress: ADDR, + freshAddressPath: "44'/354'/0'/0/0'", + freshAddresses: [], + blockHeight: 0, + operations: [], + pendingOperations: [], + currencyId: "mycoin", + unitMagnitude: 10, + lastSyncDate: "", + balance: "2111000", + }, + transactions: [ + // HERE WE WILL INSERT OUR test + ], + }, + ], + }, + }, +}; + +testBridge(dataset); +``` + +The `accounts` part is manual. This allow you to chose the best parameters for the situation you want to test. + +### Use regular Jest tests if you need more flexibility + +We tried to cover as many cases as possible that are in `getTransactionStatus`. + +You are free to define your own extra tests in `bridge.integration.test.ts` (or any other integration test file) for more advanced tests that would not be covered by the bridge generic tests. + +### Launch the test + +To launch the test do: + +```bash copy +pnpm common jest --runTestsByPath src/families//bridge.integration.test.ts +``` + +Replace `` with the name of your coin. + + +### How does a test work? + +The transaction tests simulate an object `Transaction` as input, and a `TransactionStatus` as an output that we compare with an expected status. + +There's some generic tests that are already made in `src/__tests__/test-helpers/bridge.ts` that are mandatory to pass. + +To implement your own test in `test-dataset.ts`, add an Object typed like this in the array of `transactions`: + +```ts copy +import Transaction from "./types"; + +type TestTransaction = { + name: string; + transaction: Transaction; + expectedStatus: { + amount: BigNumber; + errors: {}; + warnings: {}; + }; +}; +``` + +This `TestTransaction` uses as mainAccount the account that we have set before and then execute the command `getTransactionStatus` by using the `transaction` object as input. From 701867a930975503793444d93b00cc1fe0bd0cf3 Mon Sep 17 00:00:00 2001 From: cfranceschi-ledger Date: Tue, 10 Sep 2024 14:41:44 +0200 Subject: [PATCH 5/7] added test plan --- pages/docs/blockchain/testing.mdx | 10 +- pages/docs/blockchain/testing/_meta.json | 3 +- pages/docs/blockchain/testing/bot.mdx | 2 +- .../blockchain/testing/generic-test-plan.mdx | 106 ------------------ .../blockchain/testing/test-plan/_meta.json | 4 + .../testing/test-plan/send-receive.mdx | 36 ++++++ .../blockchain/testing/test-plan/staking.mdx | 26 +++++ pages/docs/blockchain/testing/ui-tests.mdx | 13 --- 8 files changed, 69 insertions(+), 131 deletions(-) delete mode 100644 pages/docs/blockchain/testing/generic-test-plan.mdx create mode 100644 pages/docs/blockchain/testing/test-plan/_meta.json create mode 100644 pages/docs/blockchain/testing/test-plan/send-receive.mdx create mode 100644 pages/docs/blockchain/testing/test-plan/staking.mdx delete mode 100644 pages/docs/blockchain/testing/ui-tests.mdx diff --git a/pages/docs/blockchain/testing.mdx b/pages/docs/blockchain/testing.mdx index 1774e19b..8bbe871a 100644 --- a/pages/docs/blockchain/testing.mdx +++ b/pages/docs/blockchain/testing.mdx @@ -3,15 +3,7 @@ title: Writing tests for Ledger Live integration description: "The only tests needed are Send 50% and Send max. Together these cover all the functions required." --- -# Writing Tests - -## Introduction - -The technical description of the bot in the Ledger Live repo, [here](https://github.com/LedgerHQ/ledger-live/wiki/LLC:bot). - -The only tests needed are "Send 50%" and "Send max". Together these cover all the functions required, as seen [here](https://github.com/LedgerHQ/ledger-live/wiki/LLC:bot#test-expectations). The two tests are in the code below. - -## Setting up the Test Toolchain +# Testing Before testing, make sure you have [set up the CLI](./setup-build/ledger-live-cli/). diff --git a/pages/docs/blockchain/testing/_meta.json b/pages/docs/blockchain/testing/_meta.json index 58be0e41..eb39dbb6 100644 --- a/pages/docs/blockchain/testing/_meta.json +++ b/pages/docs/blockchain/testing/_meta.json @@ -2,6 +2,5 @@ "testbridge-utility": "The testBridge utility", "bot": "Testing with the bot", "bridge-test": "Live Common Bridge Test", - "generic-test-plan": "Generic test plan", - "ui-tests": "E2E UI Tests on Desktop and Mobile" + "test-plan": "Test plan" } \ No newline at end of file diff --git a/pages/docs/blockchain/testing/bot.mdx b/pages/docs/blockchain/testing/bot.mdx index b1f1fd25..9d823a7f 100644 --- a/pages/docs/blockchain/testing/bot.mdx +++ b/pages/docs/blockchain/testing/bot.mdx @@ -401,7 +401,7 @@ export default { mycoin }; } ``` -### Token support +#### Token support ```ts copy name: "move some ERC20 like (ERC20, BEP20, etc...)", diff --git a/pages/docs/blockchain/testing/generic-test-plan.mdx b/pages/docs/blockchain/testing/generic-test-plan.mdx deleted file mode 100644 index aca9a4cc..00000000 --- a/pages/docs/blockchain/testing/generic-test-plan.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Generic test plan ---- - -# Generic test plan - -## Account management - -### Test #1 : Add new account -**Given** I’ve no _currency_ account on my portfolio -**When** I add a new account -**Then** a new _currency_ account is added on the account list -     **And** the first derived address index is **0** - - -### Test #2 : Add more accounts -**Given** I have already added a _currency_ account on my portfolio -**When** I add a new account -**Then** a new _currency_ account is added on the account list -     **And** a second derived address index is incremented - - -### Test #3 : Delete account -**Given** I already have a _currency_ account on my portfolio -**When** I delete an account -**Then** it deletes all data related to that account - - -## Synchronization - - -### Test #1 : Transaction History -**Given** I added my _currency_ account -**When** I am on my account page -**Then** it retrieves the **full** transaction history -     **And** it includes every transaction types supported by the blockchain (Tokens, if it exists) - - -### Test #2 : Balance -**Given** I have a _currency_ account with a balance -**When** I am on my account page -**Then** the account balance is correct according to how the balance is defined in the currency whether excluding or including staking amounts - - -### Test #3 : Different derivation path -**Given** I have a currency account with different derivation paths (Kepler VS Native; e.g. other wallets) -**Then** the account is retrieved when synchronizing - - -### Test #4 : Graph -**Given** I have a _currency_ account with a history -**When** I am on my account page -**Then** the graph data is correct - - -## Receive - -### Test #1 : GUI -**Given** I have a _currency_ account on my portfolio -**When** I do _Receive_ flow -**Then** the address displayed on the GUI is correct - - -### Test #2 : Device -**Given** I have a _currency_ account on my portfolio -**When** I do _Receive_ flow -**Then** the address displayed on the device is correct - - -## Send - -### Test #1 : Bad amount -**Given** I have a positive _balance_ -**When** I send _balance + 1_ to a _recipient address_ -**Then** it displays the “Insufficient funds” error - - -### Test #2 : No fees -**Given** I have a positive _balance_ -**When** I send all my _balance_ to a _recipient address_ -**Then** it displays the “Insufficient funds” error - - -### Test #3 : Bad recipient address -**Given** I have a positive _balance_ -**When** I send an _amount_ to a **bad** _recipient address_ -**Then** it displays the “Invalid address” error - - -### Test #4 : Send max -**Given** I have a positive _balance_ -**When** I do a _send max_ -     **And** toggle “Send Max” -**Then** the maximum spendable amount is (_balance_ - _fees_) -     **And** it auto-fills the amount field with the maximum spendable amount - - -### Test #5 : Self send -**Given** I have a positive _balance_ -**When** I do a _self send_ -     **And** send _amount_ to the same account address -**Then** it (or does not*) displays the“Recipient address is the same as the sender” error - -\* according to the Technical Assessment specification - - diff --git a/pages/docs/blockchain/testing/test-plan/_meta.json b/pages/docs/blockchain/testing/test-plan/_meta.json new file mode 100644 index 00000000..b5c9aa4c --- /dev/null +++ b/pages/docs/blockchain/testing/test-plan/_meta.json @@ -0,0 +1,4 @@ +{ + "send-receive": "Send/receive test plan", + "staking": "Staking feature test plan" +} \ No newline at end of file diff --git a/pages/docs/blockchain/testing/test-plan/send-receive.mdx b/pages/docs/blockchain/testing/test-plan/send-receive.mdx new file mode 100644 index 00000000..2a28ec3e --- /dev/null +++ b/pages/docs/blockchain/testing/test-plan/send-receive.mdx @@ -0,0 +1,36 @@ +# Send/receive test plan + +## Synchronization + +- Add an account +- Migrate an account : Add an account in prod, don’t crash using tested version +- Synchronizing manually does not throw an error (green check) +- Synchronizing of a big account + +## Receive/Address verification + +- User can verify his address with the nano +- User can verify his address without the nano + +## Balance + +- Available balance is right + +## Broadcast + +- Send max operation empties the account +- Send operation sends the right amount +- User cannot send more than his balance + +## Operations + +- Optimistic operation is filled correctly +- Operation history has every transactions of the account +- Operation has the right transaction ID +- User can reach the tx details by clicking on the “view on explorer” button +- Operation account is right + +## Account details + +- Fiat value fetched from countervalues is right +- User can favorite his account and it is shown \ No newline at end of file diff --git a/pages/docs/blockchain/testing/test-plan/staking.mdx b/pages/docs/blockchain/testing/test-plan/staking.mdx new file mode 100644 index 00000000..88f8764d --- /dev/null +++ b/pages/docs/blockchain/testing/test-plan/staking.mdx @@ -0,0 +1,26 @@ +# Staking feature test plan + +## Balance + +- Delegated assets value is right + +## Broadcast + +- Broadcast a transaction that contains a memo +- Broadcast a transaction that delegates more assets/ bonds more funds on a delegation +- Claim reward on a delegation +- Unbond all assets on a delegation +- Unbond 50% of assets on a delegation +- Unbond a static value of a delegation +- Add a new delegation + +## Operations + +- Operation details of claim rewards operations (1 delegation done through the Live, multiple delegations with keplr) +- Operation details of memo transaction + +## Staking + +- Delegations are all shown +- Unbounding assets are shown +- Delegation reward has the right value \ No newline at end of file diff --git a/pages/docs/blockchain/testing/ui-tests.mdx b/pages/docs/blockchain/testing/ui-tests.mdx deleted file mode 100644 index a6ed6cd2..00000000 --- a/pages/docs/blockchain/testing/ui-tests.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: E2E UI tests on Desktop and Mobile ---- - -# E2E UI tests on Desktop and Mobile - -## Testing Ledger Live Desktop - -You can test the desktop interface with Playwright, following [the process on the Ledger Live repository](https://github.com/LedgerHQ/ledger-live/wiki/LLD:Testing). - -## Testing Ledger Live Mobile - -Coming soon. From f07ef4c85f68990ec0da234a5c9bcc6792046d30 Mon Sep 17 00:00:00 2001 From: cfranceschi-ledger Date: Tue, 10 Sep 2024 14:49:08 +0200 Subject: [PATCH 6/7] adapt device speculos --- pages/docs/blockchain/testing/bot.mdx | 82 +++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/pages/docs/blockchain/testing/bot.mdx b/pages/docs/blockchain/testing/bot.mdx index 9d823a7f..063fecbf 100644 --- a/pages/docs/blockchain/testing/bot.mdx +++ b/pages/docs/blockchain/testing/bot.mdx @@ -10,6 +10,42 @@ Transaction broadcast is an exception, it is tested differently, by a tool that - An elf of the embedded app for Nano S (create a empty folder with like : `//` example `nanos/1.6.1/mycoin`. The build of your embedded app must have the following format: `app_VERSION.elf`, for example `app_1.2.3.elf`) - Some currencies of the coin + + The bot only supports Nano S Plus compatible device apps. Stax/Flex bot specs support to be included in the future.
+
+ +To migrate from nanoS speculos deviceActions to nanoS+, execute the spec manually and adapt the screen, see example below : + +```ts + { + title: "Send to address (1/3)", + button: SpeculosButton.RIGHT, + }, + { + title: "Send to address (2/3)", + button: SpeculosButton.RIGHT, + }, + { + title: "Send to address (3/3)", + button: SpeculosButton.BOTH, + }, + { + title: "Send to address (1/2)", + button: SpeculosButton.RIGHT, + }, + { + title: "Send to address (2/2)", + button: SpeculosButton.BOTH, + }, + +{ + title: "Send to address", + button: SpeculosButton.RIGHT, + }, + +``` + + ## What is this testing? We are testing the broadcast part and sync part. @@ -37,15 +73,6 @@ button: "Rr" for the bot to push the Right button of the nano || "Ll" same for l expectedValue: string of what we want to compare ``` - - - The bot only supports Nano S Plus compatible device apps. Stax/Flex bot specs support to be included in the future.
- To migrate from nanoS speculos deviceActions to nanoS+, execute the spec manually adapt the screen. -
- -+ EXEMPLE - - You can use the following example to help you start to write how the bot will react : ```ts copy @@ -541,6 +568,35 @@ Error: could not find optimisticOperation js:2:cosmos:cosmos1k2d965a5clx7327n9zx -> Backend issue +``` +necessary accounts resynced in 0.24ms +▬ Cosmos 2.35.19 on nanoS 2.1.0 +→ FROM Cosmos 2: 0.034471 ATOM (0ops) (cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935 on 44'/118'/1'/0/0) #1 js:2:cosmos:cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935: (! sum of ops 0 ATOM) 0.034471 ATOM spendable. + +max spendable ~0.03276 +★ using mutation 'send max' +→ TO Cosmos 4: 0.02964 ATOM (0ops) (cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4 on 44'/118'/3'/0/0) #3 js:2:cosmos:cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4: +✔️ transaction +SEND MAX +TO cosmos17s09a0jyp24hl7w3vcn8padz6efwmrpjwy3uf4 + +with fees=0.001713 +STATUS (816ms) + amount: 0.032758 ATOM + estimated fees: 0.001713 ATOM + total spent: 0.034471 ATOM +errors: +errors: +✔️ has been signed! (7.9s) +✔️ broadcasted! (120ms) optimistic operation: + -0.034471 ATOM OUT 8893674CA6DD3A513111B253D8D3AB4150F83CF318297B197D12BB20238ABB99 2024-04-04T05:33 +⚠️ TEST waiting operation id to appear after broadcast +Error: could not find optimisticOperation js:2:cosmos:cosmos1k2d965a5clx7327n9zx30ewz39ms7kyj9rs935:-8893674CA6DD3A513111B253D8D3AB4150F83CF318297B197D12BB20238ABB99-OUT +(totally spent 2min 9s – ends at 2024-04-04T05:55:53.870Z) +``` + +-> Backend issue + ``` necessary accounts resynced in 0.16ms ▬ Cosmos 2.35.19 on nanoS 2.1.0 @@ -565,4 +621,10 @@ errors: ⚠️ TEST during broadcast LedgerAPI5xx: API HTTP 504 (totally spent 98s – ends at 2024-04-03T11:32:38.725Z) -``` \ No newline at end of file +``` + +### How to update required screens + +Open `libs/coin-modules/coin-bitcoin/src/speculos-deviceActions.ts` and update screens according to what you see on the device app, if `expectedValue` does not make sense anymore just delete it. + +You can do a test run to see what screens fail (but the best way is to directly compare with real device app screens). \ No newline at end of file From 8a1881ed95db67b045f5bd6a97998d66f306fc37 Mon Sep 17 00:00:00 2001 From: cfranceschi-ledger Date: Tue, 24 Sep 2024 17:30:53 +0200 Subject: [PATCH 7/7] update wording --- pages/docs/blockchain/testing/test-plan/send-receive.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/docs/blockchain/testing/test-plan/send-receive.mdx b/pages/docs/blockchain/testing/test-plan/send-receive.mdx index 2a28ec3e..5e01f12d 100644 --- a/pages/docs/blockchain/testing/test-plan/send-receive.mdx +++ b/pages/docs/blockchain/testing/test-plan/send-receive.mdx @@ -5,7 +5,7 @@ - Add an account - Migrate an account : Add an account in prod, don’t crash using tested version - Synchronizing manually does not throw an error (green check) -- Synchronizing of a big account +- Synchronizing of a big account (which uses multiple pages when fetching history) ## Receive/Address verification